mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-29 12:16:55 +00:00
Linting!
Co-authored-by: Inex Code <inex.code@selfprivacy.org>
This commit is contained in:
parent
4db0413c42
commit
2ac8e4366b
|
@ -36,7 +36,6 @@ linter:
|
|||
always_declare_return_types: true
|
||||
always_put_required_named_parameters_first: true
|
||||
always_put_control_body_on_new_line: true
|
||||
always_specify_types: true
|
||||
avoid_escaping_inner_quotes: true
|
||||
avoid_setters_without_getters: true
|
||||
eol_at_end_of_file: true
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||
|
@ -20,14 +18,14 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
const bool isDark = false;
|
||||
final ServerInstallationCubit serverInstallationCubit = ServerInstallationCubit()..load();
|
||||
final UsersCubit usersCubit = UsersCubit(serverInstallationCubit);
|
||||
final ServicesCubit servicesCubit = ServicesCubit(serverInstallationCubit);
|
||||
final BackupsCubit backupsCubit = BackupsCubit(serverInstallationCubit);
|
||||
final DnsRecordsCubit dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit);
|
||||
final RecoveryKeyCubit recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit);
|
||||
final ApiDevicesCubit apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit);
|
||||
const isDark = false;
|
||||
final serverInstallationCubit = ServerInstallationCubit()..load();
|
||||
final usersCubit = UsersCubit(serverInstallationCubit);
|
||||
final servicesCubit = ServicesCubit(serverInstallationCubit);
|
||||
final backupsCubit = BackupsCubit(serverInstallationCubit);
|
||||
final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit);
|
||||
final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit);
|
||||
final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit);
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
|
@ -36,14 +34,32 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
isOnboardingShowing: true,
|
||||
)..load(),
|
||||
),
|
||||
BlocProvider(create: (final _) => serverInstallationCubit, lazy: false),
|
||||
BlocProvider(
|
||||
create: (final _) => serverInstallationCubit,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(create: (final _) => ProvidersCubit()),
|
||||
BlocProvider(create: (final _) => usersCubit..load(), lazy: false),
|
||||
BlocProvider(create: (final _) => servicesCubit..load(), lazy: false),
|
||||
BlocProvider(create: (final _) => backupsCubit..load(), lazy: false),
|
||||
BlocProvider(create: (final _) => dnsRecordsCubit..load()),
|
||||
BlocProvider(create: (final _) => recoveryKeyCubit..load()),
|
||||
BlocProvider(create: (final _) => apiDevicesCubit..load()),
|
||||
BlocProvider(
|
||||
create: (final _) => usersCubit..load(),
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => servicesCubit..load(),
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => backupsCubit..load(),
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => dnsRecordsCubit..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => recoveryKeyCubit..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => apiDevicesCubit..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) =>
|
||||
JobsCubit(usersCubit: usersCubit, servicesCubit: servicesCubit),
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/ui/components/error/error.dart';
|
||||
|
@ -11,7 +9,11 @@ class SimpleBlocObserver extends BlocObserver {
|
|||
SimpleBlocObserver();
|
||||
|
||||
@override
|
||||
void onError(final BlocBase bloc, final Object error, final StackTrace stackTrace) {
|
||||
void onError(
|
||||
final BlocBase<dynamic> bloc,
|
||||
final Object error,
|
||||
final StackTrace stackTrace,
|
||||
) {
|
||||
final NavigatorState navigator = getIt.get<NavigationService>().navigator!;
|
||||
|
||||
navigator.push(
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BrandColors {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -12,11 +10,11 @@ class Localization extends StatelessWidget {
|
|||
final Widget? child;
|
||||
@override
|
||||
Widget build(final BuildContext context) => EasyLocalization(
|
||||
supportedLocales: const [Locale('ru'), Locale('en')],
|
||||
path: 'assets/translations',
|
||||
fallbackLocale: const Locale('en'),
|
||||
saveLocale: false,
|
||||
useOnlyLangCode: true,
|
||||
child: child!,
|
||||
);
|
||||
supportedLocales: const [Locale('ru'), Locale('en')],
|
||||
path: 'assets/translations',
|
||||
fallbackLocale: const Locale('en'),
|
||||
saveLocale: false,
|
||||
useOnlyLangCode: true,
|
||||
child: child!,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
@ -20,19 +18,24 @@ abstract class ApiMap {
|
|||
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
||||
(final HttpClient client) {
|
||||
client.badCertificateCallback =
|
||||
(final X509Certificate cert, final String host, final int port) => true;
|
||||
(final X509Certificate cert, final String host, final int port) =>
|
||||
true;
|
||||
return client;
|
||||
};
|
||||
|
||||
dio.interceptors.add(InterceptorsWrapper(onError: (final DioError e, final ErrorInterceptorHandler handler) {
|
||||
print(e.requestOptions.path);
|
||||
print(e.requestOptions.data);
|
||||
dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onError: (final DioError e, final ErrorInterceptorHandler handler) {
|
||||
print(e.requestOptions.path);
|
||||
print(e.requestOptions.data);
|
||||
|
||||
print(e.message);
|
||||
print(e.response);
|
||||
print(e.message);
|
||||
print(e.response);
|
||||
|
||||
return handler.next(e);
|
||||
},),);
|
||||
return handler.next(e);
|
||||
},
|
||||
),
|
||||
);
|
||||
return dio;
|
||||
}
|
||||
|
||||
|
@ -56,7 +59,7 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
}
|
||||
|
||||
@override
|
||||
Future onRequest(
|
||||
Future<void> onRequest(
|
||||
final RequestOptions options,
|
||||
final RequestInterceptorHandler handler,
|
||||
) async {
|
||||
|
@ -70,7 +73,7 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
}
|
||||
|
||||
@override
|
||||
Future onResponse(
|
||||
Future<void> onResponse(
|
||||
final Response response,
|
||||
final ResponseInterceptorHandler handler,
|
||||
) async {
|
||||
|
@ -87,7 +90,10 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
}
|
||||
|
||||
@override
|
||||
Future onError(final DioError err, final ErrorInterceptorHandler handler) async {
|
||||
Future<void> onError(
|
||||
final DioError err,
|
||||
final ErrorInterceptorHandler handler,
|
||||
) async {
|
||||
final Response? response = err.response;
|
||||
log(err.toString());
|
||||
addMessage(
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
@ -15,8 +13,10 @@ class BackblazeApiAuth {
|
|||
}
|
||||
|
||||
class BackblazeApplicationKey {
|
||||
BackblazeApplicationKey(
|
||||
{required this.applicationKeyId, required this.applicationKey,});
|
||||
BackblazeApplicationKey({
|
||||
required this.applicationKeyId,
|
||||
required this.applicationKey,
|
||||
});
|
||||
|
||||
final String applicationKeyId;
|
||||
final String applicationKey;
|
||||
|
@ -29,7 +29,8 @@ class BackblazeApi extends ApiMap {
|
|||
BaseOptions get options {
|
||||
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
|
||||
if (isWithToken) {
|
||||
final BackblazeCredential? backblazeCredential = getIt<ApiConfigModel>().backblazeCredential;
|
||||
final BackblazeCredential? backblazeCredential =
|
||||
getIt<ApiConfigModel>().backblazeCredential;
|
||||
final String token = backblazeCredential!.applicationKey;
|
||||
options.headers = {'Authorization': 'Basic $token'};
|
||||
}
|
||||
|
@ -48,12 +49,15 @@ class BackblazeApi extends ApiMap {
|
|||
|
||||
Future<BackblazeApiAuth> getAuthorizationToken() async {
|
||||
final Dio client = await getClient();
|
||||
final BackblazeCredential? backblazeCredential = getIt<ApiConfigModel>().backblazeCredential;
|
||||
final BackblazeCredential? backblazeCredential =
|
||||
getIt<ApiConfigModel>().backblazeCredential;
|
||||
if (backblazeCredential == null) {
|
||||
throw Exception('Backblaze credential is null');
|
||||
}
|
||||
final String encodedApiKey = encodedBackblazeKey(
|
||||
backblazeCredential.keyId, backblazeCredential.applicationKey,);
|
||||
backblazeCredential.keyId,
|
||||
backblazeCredential.applicationKey,
|
||||
);
|
||||
final Response response = await client.get(
|
||||
'b2_authorize_account',
|
||||
options: Options(headers: {'Authorization': 'Basic $encodedApiKey'}),
|
||||
|
@ -89,7 +93,8 @@ class BackblazeApi extends ApiMap {
|
|||
// Create bucket
|
||||
Future<String> createBucket(final String bucketName) async {
|
||||
final BackblazeApiAuth auth = await getAuthorizationToken();
|
||||
final BackblazeCredential? backblazeCredential = getIt<ApiConfigModel>().backblazeCredential;
|
||||
final BackblazeCredential? backblazeCredential =
|
||||
getIt<ApiConfigModel>().backblazeCredential;
|
||||
final Dio client = await getClient();
|
||||
client.options.baseUrl = auth.apiUrl;
|
||||
final Response response = await client.post(
|
||||
|
@ -138,8 +143,9 @@ class BackblazeApi extends ApiMap {
|
|||
close(client);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return BackblazeApplicationKey(
|
||||
applicationKeyId: response.data['applicationKeyId'],
|
||||
applicationKey: response.data['applicationKey'],);
|
||||
applicationKeyId: response.data['applicationKeyId'],
|
||||
applicationKey: response.data['applicationKey'],
|
||||
);
|
||||
} else {
|
||||
throw Exception('code: ${response.statusCode}');
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
@ -14,7 +12,6 @@ class DomainNotFoundException implements Exception {
|
|||
}
|
||||
|
||||
class CloudflareApi extends ApiMap {
|
||||
|
||||
CloudflareApi({
|
||||
this.hasLogger = false,
|
||||
this.isWithToken = true,
|
||||
|
@ -50,11 +47,14 @@ class CloudflareApi extends ApiMap {
|
|||
String rootAddress = 'https://api.cloudflare.com/client/v4';
|
||||
|
||||
Future<bool> isValid(final String token) async {
|
||||
validateStatus = (final status) => status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||
validateStatus = (final status) =>
|
||||
status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get('/user/tokens/verify',
|
||||
options: Options(headers: {'Authorization': 'Bearer $token'}),);
|
||||
final Response response = await client.get(
|
||||
'/user/tokens/verify',
|
||||
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
||||
);
|
||||
|
||||
close(client);
|
||||
|
||||
|
@ -68,7 +68,8 @@ class CloudflareApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<String> getZoneId(final String domain) async {
|
||||
validateStatus = (final status) => status == HttpStatus.ok || status == HttpStatus.forbidden;
|
||||
validateStatus = (final status) =>
|
||||
status == HttpStatus.ok || status == HttpStatus.forbidden;
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get(
|
||||
'/zones',
|
||||
|
@ -127,13 +128,15 @@ class CloudflareApi extends ApiMap {
|
|||
|
||||
for (final 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'],
|
||||
),);
|
||||
allRecords.add(
|
||||
DnsRecord(
|
||||
name: record['name'],
|
||||
type: record['type'],
|
||||
content: record['content'],
|
||||
ttl: record['ttl'],
|
||||
proxied: record['proxied'],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,16 +172,22 @@ class CloudflareApi extends ApiMap {
|
|||
}
|
||||
}
|
||||
|
||||
List<DnsRecord> projectDnsRecords(final String? domainName, final String? ip4) {
|
||||
final DnsRecord domainA = DnsRecord(type: 'A', name: domainName, content: ip4);
|
||||
List<DnsRecord> projectDnsRecords(
|
||||
final String? domainName,
|
||||
final String? ip4,
|
||||
) {
|
||||
final DnsRecord domainA =
|
||||
DnsRecord(type: 'A', name: domainName, content: ip4);
|
||||
|
||||
final DnsRecord mx = DnsRecord(type: 'MX', name: '@', content: domainName);
|
||||
final DnsRecord apiA = DnsRecord(type: 'A', name: 'api', content: ip4);
|
||||
final DnsRecord cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4);
|
||||
final DnsRecord gitA = DnsRecord(type: 'A', name: 'git', content: ip4);
|
||||
final DnsRecord meetA = DnsRecord(type: 'A', name: 'meet', content: ip4);
|
||||
final DnsRecord passwordA = DnsRecord(type: 'A', name: 'password', content: ip4);
|
||||
final DnsRecord socialA = DnsRecord(type: 'A', name: 'social', content: ip4);
|
||||
final DnsRecord passwordA =
|
||||
DnsRecord(type: 'A', name: 'password', content: ip4);
|
||||
final DnsRecord socialA =
|
||||
DnsRecord(type: 'A', name: 'social', content: ip4);
|
||||
final DnsRecord vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
|
||||
|
||||
final DnsRecord txt1 = DnsRecord(
|
||||
|
@ -211,7 +220,9 @@ class CloudflareApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<void> setDkim(
|
||||
final String dkimRecordString, final ServerDomain cloudFlareDomain,) async {
|
||||
final String dkimRecordString,
|
||||
final ServerDomain cloudFlareDomain,
|
||||
) async {
|
||||
final String domainZoneId = cloudFlareDomain.zoneId;
|
||||
final String url = '$rootAddress/zones/$domainZoneId/dns_records';
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
|
@ -12,7 +10,6 @@ import 'package:selfprivacy/logic/models/hive/user.dart';
|
|||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
class HetznerApi extends ApiMap {
|
||||
|
||||
HetznerApi({this.hasLogger = false, this.isWithToken = true});
|
||||
@override
|
||||
bool hasLogger;
|
||||
|
@ -39,7 +36,8 @@ class HetznerApi extends ApiMap {
|
|||
String rootAddress = 'https://api.hetzner.cloud/v1';
|
||||
|
||||
Future<bool> isValid(final String token) async {
|
||||
validateStatus = (final int? status) => status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||
validateStatus = (final int? status) =>
|
||||
status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get(
|
||||
'/servers',
|
||||
|
@ -201,8 +199,12 @@ class HetznerApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<Map<String, dynamic>> getMetrics(
|
||||
final DateTime start, final DateTime end, final String type,) async {
|
||||
final ServerHostingDetails? hetznerServer = getIt<ApiConfigModel>().serverDetails;
|
||||
final DateTime start,
|
||||
final DateTime end,
|
||||
final String type,
|
||||
) async {
|
||||
final ServerHostingDetails? hetznerServer =
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
final Dio client = await getClient();
|
||||
|
||||
final Map<String, dynamic> queryParameters = {
|
||||
|
@ -219,7 +221,8 @@ class HetznerApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<HetznerServerInfo> getInfo() async {
|
||||
final ServerHostingDetails? hetznerServer = getIt<ApiConfigModel>().serverDetails;
|
||||
final ServerHostingDetails? hetznerServer =
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get('/servers/${hetznerServer!.id}');
|
||||
close(client);
|
||||
|
@ -233,6 +236,7 @@ class HetznerApi extends ApiMap {
|
|||
close(client);
|
||||
|
||||
return (response.data!['servers'] as List)
|
||||
// ignore: unnecessary_lambdas
|
||||
.map((final e) => HetznerServerInfo.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
@ -241,7 +245,8 @@ class HetznerApi extends ApiMap {
|
|||
required final String ip4,
|
||||
required final String domainName,
|
||||
}) async {
|
||||
final ServerHostingDetails? hetznerServer = getIt<ApiConfigModel>().serverDetails;
|
||||
final ServerHostingDetails? hetznerServer =
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
final Dio client = await getClient();
|
||||
await client.post(
|
||||
'/servers/${hetznerServer!.id}/actions/change_dns_ptr',
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
@ -20,7 +18,6 @@ import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
|||
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||
|
||||
class ApiResponse<D> {
|
||||
|
||||
ApiResponse({
|
||||
required this.statusCode,
|
||||
required this.data,
|
||||
|
@ -34,12 +31,12 @@ class ApiResponse<D> {
|
|||
}
|
||||
|
||||
class ServerApi extends ApiMap {
|
||||
|
||||
ServerApi(
|
||||
{this.hasLogger = false,
|
||||
this.isWithToken = true,
|
||||
this.overrideDomain,
|
||||
this.customToken,});
|
||||
ServerApi({
|
||||
this.hasLogger = false,
|
||||
this.isWithToken = true,
|
||||
this.overrideDomain,
|
||||
this.customToken,
|
||||
});
|
||||
@override
|
||||
bool hasLogger;
|
||||
@override
|
||||
|
@ -52,13 +49,17 @@ class ServerApi extends ApiMap {
|
|||
BaseOptions options = BaseOptions();
|
||||
|
||||
if (isWithToken) {
|
||||
final ServerDomain? cloudFlareDomain = getIt<ApiConfigModel>().serverDomain;
|
||||
final ServerDomain? cloudFlareDomain =
|
||||
getIt<ApiConfigModel>().serverDomain;
|
||||
final String domainName = cloudFlareDomain!.domainName;
|
||||
final String? apiToken = getIt<ApiConfigModel>().serverDetails?.apiToken;
|
||||
|
||||
options = BaseOptions(baseUrl: 'https://api.$domainName', headers: {
|
||||
'Authorization': 'Bearer $apiToken',
|
||||
},);
|
||||
options = BaseOptions(
|
||||
baseUrl: 'https://api.$domainName',
|
||||
headers: {
|
||||
'Authorization': 'Bearer $apiToken',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (overrideDomain != null) {
|
||||
|
@ -157,14 +158,18 @@ class ServerApi extends ApiMap {
|
|||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<List<String>>> getUsersList({final withMainUser = false}) async {
|
||||
Future<ApiResponse<List<String>>> getUsersList({
|
||||
final withMainUser = false,
|
||||
}) async {
|
||||
final List<String> res = [];
|
||||
Response response;
|
||||
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
response = await client.get('/users',
|
||||
queryParameters: withMainUser ? {'withMainUser': 'true'} : null,);
|
||||
response = await client.get(
|
||||
'/users',
|
||||
queryParameters: withMainUser ? {'withMainUser': 'true'} : null,
|
||||
);
|
||||
for (final user in response.data) {
|
||||
res.add(user.toString());
|
||||
}
|
||||
|
@ -194,7 +199,10 @@ class ServerApi extends ApiMap {
|
|||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<void>> addUserSshKey(final User user, final String sshKey) async {
|
||||
Future<ApiResponse<void>> addUserSshKey(
|
||||
final User user,
|
||||
final String sshKey,
|
||||
) async {
|
||||
late Response response;
|
||||
|
||||
final Dio client = await getClient();
|
||||
|
@ -259,7 +267,9 @@ class ServerApi extends ApiMap {
|
|||
final Dio client = await getClient();
|
||||
try {
|
||||
response = await client.get('/services/ssh/keys/${user.login}');
|
||||
res = (response.data as List<dynamic>).map((final e) => e as String).toList();
|
||||
res = (response.data as List<dynamic>)
|
||||
.map((final e) => e as String)
|
||||
.toList();
|
||||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse<List<String>>(
|
||||
|
@ -290,7 +300,10 @@ class ServerApi extends ApiMap {
|
|||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<void>> deleteUserSshKey(final User user, final String sshKey) async {
|
||||
Future<ApiResponse<void>> deleteUserSshKey(
|
||||
final User user,
|
||||
final String sshKey,
|
||||
) async {
|
||||
Response response;
|
||||
|
||||
final Dio client = await getClient();
|
||||
|
@ -360,7 +373,10 @@ class ServerApi extends ApiMap {
|
|||
return res;
|
||||
}
|
||||
|
||||
Future<void> switchService(final ServiceTypes type, final bool needToTurnOn) async {
|
||||
Future<void> switchService(
|
||||
final ServiceTypes type,
|
||||
final bool needToTurnOn,
|
||||
) async {
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
client.post(
|
||||
|
@ -431,7 +447,7 @@ class ServerApi extends ApiMap {
|
|||
final Dio client = await getClient();
|
||||
try {
|
||||
response = await client.get('/services/restic/backup/list');
|
||||
backups = response.data.map<Backup>((final e) => Backup.fromJson(e)).toList();
|
||||
backups = response.data.map<Backup>(Backup.fromJson).toList();
|
||||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
} catch (e) {
|
||||
|
@ -562,7 +578,9 @@ class ServerApi extends ApiMap {
|
|||
return settings;
|
||||
}
|
||||
|
||||
Future<void> updateAutoUpgradeSettings(final AutoUpgradeSettings settings) async {
|
||||
Future<void> updateAutoUpgradeSettings(
|
||||
final AutoUpgradeSettings settings,
|
||||
) async {
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
await client.put(
|
||||
|
@ -579,7 +597,8 @@ class ServerApi extends ApiMap {
|
|||
Future<TimeZoneSettings> getServerTimezone() async {
|
||||
// I am not sure how to initialize TimeZoneSettings with default value...
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get('/system/configuration/timezone');
|
||||
final Response response =
|
||||
await client.get('/system/configuration/timezone');
|
||||
close(client);
|
||||
|
||||
return TimeZoneSettings.fromString(response.data);
|
||||
|
@ -642,9 +661,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: const RecoveryKeyStatus(exists: false, valid: false),);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: const RecoveryKeyStatus(exists: false, valid: false),
|
||||
);
|
||||
} finally {
|
||||
close(client);
|
||||
}
|
||||
|
@ -652,10 +672,11 @@ class ServerApi extends ApiMap {
|
|||
final int code = response.statusCode ?? HttpStatus.internalServerError;
|
||||
|
||||
return ApiResponse(
|
||||
statusCode: code,
|
||||
data: response.data != null
|
||||
? RecoveryKeyStatus.fromJson(response.data)
|
||||
: null,);
|
||||
statusCode: code,
|
||||
data: response.data != null
|
||||
? RecoveryKeyStatus.fromJson(response.data)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<String>> generateRecoveryToken(
|
||||
|
@ -681,9 +702,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',
|
||||
);
|
||||
} finally {
|
||||
close(client);
|
||||
}
|
||||
|
@ -691,8 +713,9 @@ class ServerApi extends ApiMap {
|
|||
final int code = response.statusCode ?? HttpStatus.internalServerError;
|
||||
|
||||
return ApiResponse(
|
||||
statusCode: code,
|
||||
data: response.data != null ? response.data['token'] : '',);
|
||||
statusCode: code,
|
||||
data: response.data != null ? response.data['token'] : '',
|
||||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<String>> useRecoveryToken(final DeviceToken token) async {
|
||||
|
@ -710,9 +733,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',
|
||||
);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
@ -720,8 +744,9 @@ class ServerApi extends ApiMap {
|
|||
final int code = response.statusCode ?? HttpStatus.internalServerError;
|
||||
|
||||
return ApiResponse(
|
||||
statusCode: code,
|
||||
data: response.data != null ? response.data['token'] : '',);
|
||||
statusCode: code,
|
||||
data: response.data != null ? response.data['token'] : '',
|
||||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<String>> authorizeDevice(final DeviceToken token) async {
|
||||
|
@ -739,9 +764,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',
|
||||
);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
@ -760,9 +786,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',
|
||||
);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
@ -770,8 +797,9 @@ class ServerApi extends ApiMap {
|
|||
final int code = response.statusCode ?? HttpStatus.internalServerError;
|
||||
|
||||
return ApiResponse(
|
||||
statusCode: code,
|
||||
data: response.data != null ? response.data['token'] : '',);
|
||||
statusCode: code,
|
||||
data: response.data != null ? response.data['token'] : '',
|
||||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<String>> deleteDeviceToken() async {
|
||||
|
@ -783,9 +811,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',
|
||||
);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
@ -804,9 +833,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: [],);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: [],
|
||||
);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
@ -814,10 +844,11 @@ class ServerApi extends ApiMap {
|
|||
final int code = response.statusCode ?? HttpStatus.internalServerError;
|
||||
|
||||
return ApiResponse(
|
||||
statusCode: code,
|
||||
data: (response.data != null)
|
||||
? response.data.map<ApiToken>((final e) => ApiToken.fromJson(e)).toList()
|
||||
: [],);
|
||||
statusCode: code,
|
||||
data: (response.data != null)
|
||||
? response.data.map<ApiToken>(ApiToken.fromJson).toList()
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<String>> refreshCurrentApiToken() async {
|
||||
|
@ -829,9 +860,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: '',
|
||||
);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
@ -839,8 +871,9 @@ class ServerApi extends ApiMap {
|
|||
final int code = response.statusCode ?? HttpStatus.internalServerError;
|
||||
|
||||
return ApiResponse(
|
||||
statusCode: code,
|
||||
data: response.data != null ? response.data['token'] : '',);
|
||||
statusCode: code,
|
||||
data: response.data != null ? response.data['token'] : '',
|
||||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse<void>> deleteApiToken(final String device) async {
|
||||
|
@ -856,9 +889,10 @@ class ServerApi extends ApiMap {
|
|||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
return ApiResponse(
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: null,);
|
||||
errorMessage: e.message,
|
||||
statusCode: e.response?.statusCode ?? HttpStatus.internalServerError,
|
||||
data: null,
|
||||
);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -25,10 +23,12 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
|
|||
void load() {
|
||||
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
|
||||
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
|
||||
emit(state.copyWith(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing,
|
||||
),);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void updateDarkMode({required final bool isDarkModeOn}) {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'app_settings_cubit.dart';
|
||||
|
||||
class AppSettingsState extends Equatable {
|
||||
|
@ -11,7 +9,10 @@ class AppSettingsState extends Equatable {
|
|||
final bool isDarkModeOn;
|
||||
final bool isOnboardingShowing;
|
||||
|
||||
AppSettingsState copyWith({final isDarkModeOn, final isOnboardingShowing}) =>
|
||||
AppSettingsState copyWith({
|
||||
final bool? isDarkModeOn,
|
||||
final bool? isOnboardingShowing,
|
||||
}) =>
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
@ -15,7 +13,9 @@ part 'backups_state.dart';
|
|||
class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
|
||||
BackupsCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(
|
||||
serverInstallationCubit, const BackupsState(preventActions: true),);
|
||||
serverInstallationCubit,
|
||||
const BackupsState(preventActions: true),
|
||||
);
|
||||
|
||||
final ServerApi api = ServerApi();
|
||||
final BackblazeApi backblaze = BackblazeApi();
|
||||
|
@ -25,59 +25,72 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
|
|||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
if (bucket == null) {
|
||||
emit(const BackupsState(
|
||||
isInitialized: false, preventActions: false, refreshing: false,),);
|
||||
emit(
|
||||
const BackupsState(
|
||||
isInitialized: false,
|
||||
preventActions: false,
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final BackupStatus status = await api.getBackupStatus();
|
||||
switch (status.status) {
|
||||
case BackupStatusEnum.noKey:
|
||||
case BackupStatusEnum.notInitialized:
|
||||
emit(BackupsState(
|
||||
backups: const [],
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: 0,
|
||||
status: status.status,
|
||||
refreshing: false,
|
||||
),);
|
||||
emit(
|
||||
BackupsState(
|
||||
backups: const [],
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: 0,
|
||||
status: status.status,
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case BackupStatusEnum.initializing:
|
||||
emit(BackupsState(
|
||||
backups: const [],
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: 0,
|
||||
status: status.status,
|
||||
refreshTimer: const Duration(seconds: 10),
|
||||
refreshing: false,
|
||||
),);
|
||||
emit(
|
||||
BackupsState(
|
||||
backups: const [],
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: 0,
|
||||
status: status.status,
|
||||
refreshTimer: const Duration(seconds: 10),
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case BackupStatusEnum.initialized:
|
||||
case BackupStatusEnum.error:
|
||||
final List<Backup> backups = await api.getBackups();
|
||||
emit(BackupsState(
|
||||
backups: backups,
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage ?? '',
|
||||
refreshing: false,
|
||||
),);
|
||||
emit(
|
||||
BackupsState(
|
||||
backups: backups,
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage ?? '',
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case BackupStatusEnum.backingUp:
|
||||
case BackupStatusEnum.restoring:
|
||||
final List<Backup> backups = await api.getBackups();
|
||||
emit(BackupsState(
|
||||
backups: backups,
|
||||
isInitialized: true,
|
||||
preventActions: true,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage ?? '',
|
||||
refreshTimer: const Duration(seconds: 5),
|
||||
refreshing: false,
|
||||
),);
|
||||
emit(
|
||||
BackupsState(
|
||||
backups: backups,
|
||||
isInitialized: true,
|
||||
preventActions: true,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage ?? '',
|
||||
refreshTimer: const Duration(seconds: 5),
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
emit(const BackupsState());
|
||||
|
@ -101,10 +114,11 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
|
|||
|
||||
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
|
||||
final BackblazeBucket bucket = BackblazeBucket(
|
||||
bucketId: bucketId,
|
||||
bucketName: bucketName,
|
||||
applicationKey: key.applicationKey,
|
||||
applicationKeyId: key.applicationKeyId,);
|
||||
bucketId: bucketId,
|
||||
bucketName: bucketName,
|
||||
applicationKey: key.applicationKey,
|
||||
applicationKeyId: key.applicationKeyId,
|
||||
);
|
||||
|
||||
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket);
|
||||
await api.uploadBackblazeConfig(bucket);
|
||||
|
@ -141,14 +155,16 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
|
|||
emit(state.copyWith(refreshing: true));
|
||||
final List<Backup> backups = await api.getBackups();
|
||||
final BackupStatus status = await api.getBackupStatus();
|
||||
emit(state.copyWith(
|
||||
backups: backups,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage,
|
||||
refreshTimer: refreshTimeFromState(status.status),
|
||||
refreshing: false,
|
||||
),);
|
||||
emit(
|
||||
state.copyWith(
|
||||
backups: backups,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage,
|
||||
refreshTimer: refreshTimeFromState(status.status),
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
if (useTimer) {
|
||||
Timer(state.refreshTimer, () => updateBackups(useTimer: true));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'backups_cubit.dart';
|
||||
|
||||
class BackupsState extends ServerInstallationDependendState {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
|
@ -50,9 +48,12 @@ class ApiDevicesCubit
|
|||
Future<void> deleteDevice(final ApiToken device) async {
|
||||
final ApiResponse<void> response = await api.deleteApiToken(device.name);
|
||||
if (response.isSuccess) {
|
||||
emit(ApiDevicesState(
|
||||
emit(
|
||||
ApiDevicesState(
|
||||
state.devices.where((final d) => d.name != device.name).toList(),
|
||||
LoadingStatus.success,),);
|
||||
LoadingStatus.success,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(response.errorMessage ?? 'Error deleting device');
|
||||
|
@ -65,7 +66,8 @@ class ApiDevicesCubit
|
|||
return response.data;
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
response.errorMessage ?? 'Error getting new device key',);
|
||||
response.errorMessage ?? 'Error getting new device key',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'devices_cubit.dart';
|
||||
|
||||
class ApiDevicesState extends ServerInstallationDependendState {
|
||||
|
@ -10,12 +8,14 @@ class ApiDevicesState extends ServerInstallationDependendState {
|
|||
final LoadingStatus status;
|
||||
|
||||
List<ApiToken> get devices => _devices;
|
||||
ApiToken get thisDevice => _devices.firstWhere((final device) => device.isCaller,
|
||||
orElse: () => ApiToken(
|
||||
name: 'Error fetching device',
|
||||
isCaller: true,
|
||||
date: DateTime.now(),
|
||||
),);
|
||||
ApiToken get thisDevice => _devices.firstWhere(
|
||||
(final device) => device.isCaller,
|
||||
orElse: () => ApiToken(
|
||||
name: 'Error fetching device',
|
||||
isCaller: true,
|
||||
date: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
List<ApiToken> get otherDevices =>
|
||||
_devices.where((final device) => !device.isCaller).toList();
|
||||
|
@ -23,10 +23,11 @@ class ApiDevicesState extends ServerInstallationDependendState {
|
|||
ApiDevicesState copyWith({
|
||||
final List<ApiToken>? devices,
|
||||
final LoadingStatus? status,
|
||||
}) => ApiDevicesState(
|
||||
devices ?? _devices,
|
||||
status ?? this.status,
|
||||
);
|
||||
}) =>
|
||||
ApiDevicesState(
|
||||
devices ?? _devices,
|
||||
status ?? this.status,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [_devices];
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
|
@ -13,18 +11,26 @@ part 'dns_records_state.dart';
|
|||
class DnsRecordsCubit
|
||||
extends ServerInstallationDependendCubit<DnsRecordsState> {
|
||||
DnsRecordsCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(serverInstallationCubit,
|
||||
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),);
|
||||
: super(
|
||||
serverInstallationCubit,
|
||||
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),
|
||||
);
|
||||
|
||||
final ServerApi api = ServerApi();
|
||||
final CloudflareApi cloudflare = CloudflareApi();
|
||||
|
||||
@override
|
||||
Future<void> load() async {
|
||||
emit(DnsRecordsState(
|
||||
emit(
|
||||
DnsRecordsState(
|
||||
dnsState: DnsRecordsStatus.refreshing,
|
||||
dnsRecords: _getDesiredDnsRecords(
|
||||
serverInstallationCubit.state.serverDomain?.domainName, '', '',),),);
|
||||
serverInstallationCubit.state.serverDomain?.domainName,
|
||||
'',
|
||||
'',
|
||||
),
|
||||
),
|
||||
);
|
||||
print('Loading DNS status');
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||
|
@ -41,41 +47,48 @@ class DnsRecordsCubit
|
|||
if (record.description ==
|
||||
'providers.domain.record_description.dkim') {
|
||||
final DnsRecord foundRecord = records.firstWhere(
|
||||
(final r) => r.name == record.name && r.type == record.type,
|
||||
orElse: () => DnsRecord(
|
||||
name: record.name,
|
||||
type: record.type,
|
||||
content: '',
|
||||
ttl: 800,
|
||||
proxied: false,),);
|
||||
(final 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 String? foundContent =
|
||||
foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
|
||||
final String content = record.content.replaceAll(RegExp(r'\s+'), '');
|
||||
final String 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((final r) =>
|
||||
r.name == record.name &&
|
||||
r.type == record.type &&
|
||||
r.content == record.content,)) {
|
||||
if (records.any(
|
||||
(final 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((final r) => r.isSatisfied == false)
|
||||
? DnsRecordsStatus.error
|
||||
: DnsRecordsStatus.good,
|
||||
),);
|
||||
emit(
|
||||
DnsRecordsState(
|
||||
dnsRecords: foundRecords,
|
||||
dnsState: foundRecords.any((final r) => r.isSatisfied == false)
|
||||
? DnsRecordsStatus.error
|
||||
: DnsRecordsStatus.good,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(const DnsRecordsState());
|
||||
}
|
||||
|
@ -105,13 +118,18 @@ class DnsRecordsCubit
|
|||
final String? dkimPublicKey = await api.getDkim();
|
||||
await cloudflare.removeSimilarRecords(cloudFlareDomain: domain!);
|
||||
await cloudflare.createMultipleDnsRecords(
|
||||
cloudFlareDomain: domain, ip4: ipAddress,);
|
||||
cloudFlareDomain: domain,
|
||||
ip4: ipAddress,
|
||||
);
|
||||
await cloudflare.setDkim(dkimPublicKey ?? '', domain);
|
||||
await load();
|
||||
}
|
||||
|
||||
List<DesiredDnsRecord> _getDesiredDnsRecords(
|
||||
final String? domainName, final String? ipAddress, final String? dkimPublicKey,) {
|
||||
final String? domainName,
|
||||
final String? ipAddress,
|
||||
final String? dkimPublicKey,
|
||||
) {
|
||||
if (domainName == null || ipAddress == null || dkimPublicKey == null) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'dns_records_cubit.dart';
|
||||
|
||||
enum DnsRecordsStatus {
|
||||
|
@ -33,10 +31,11 @@ class DnsRecordsState extends ServerInstallationDependendState {
|
|||
DnsRecordsState copyWith({
|
||||
final DnsRecordsStatus? dnsState,
|
||||
final List<DesiredDnsRecord>? dnsRecords,
|
||||
}) => DnsRecordsState(
|
||||
dnsState: dnsState ?? this.dnsState,
|
||||
dnsRecords: dnsRecords ?? this.dnsRecords,
|
||||
);
|
||||
}) =>
|
||||
DnsRecordsState(
|
||||
dnsState: dnsState ?? this.dnsState,
|
||||
dnsRecords: dnsRecords ?? this.dnsRecords,
|
||||
);
|
||||
}
|
||||
|
||||
class DesiredDnsRecord {
|
||||
|
@ -63,12 +62,13 @@ class DesiredDnsRecord {
|
|||
final String? description,
|
||||
final DnsRecordsCategory? category,
|
||||
final bool? isSatisfied,
|
||||
}) => 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,
|
||||
);
|
||||
}) =>
|
||||
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,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
@ -24,15 +22,20 @@ class FieldCubitFactory {
|
|||
initalValue: '',
|
||||
validations: [
|
||||
ValidationModel<String>(
|
||||
(final String s) => s.toLowerCase() == 'root', 'validations.root_name'.tr(),),
|
||||
(final String s) => s.toLowerCase() == 'root',
|
||||
'validations.root_name'.tr(),
|
||||
),
|
||||
ValidationModel(
|
||||
(final String login) => context.read<UsersCubit>().state.isLoginRegistered(login),
|
||||
(final String login) =>
|
||||
context.read<UsersCubit>().state.isLoginRegistered(login),
|
||||
'validations.user_already_exist'.tr(),
|
||||
),
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
LengthStringLongerValidation(userMaxLength),
|
||||
ValidationModel<String>((final String s) => !userAllowedRegExp.hasMatch(s),
|
||||
'validations.invalid_format'.tr(),),
|
||||
ValidationModel<String>(
|
||||
(final String s) => !userAllowedRegExp.hasMatch(s),
|
||||
'validations.invalid_format'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -48,18 +51,19 @@ class FieldCubitFactory {
|
|||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
ValidationModel<String>(
|
||||
passwordForbiddenRegExp.hasMatch,
|
||||
'validations.invalid_format'.tr(),),
|
||||
passwordForbiddenRegExp.hasMatch,
|
||||
'validations.invalid_format'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
FieldCubit<String> createRequiredStringField() => FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
],
|
||||
);
|
||||
initalValue: '',
|
||||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
],
|
||||
);
|
||||
|
||||
final BuildContext context;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/backblaze.dart';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
|
@ -16,7 +14,9 @@ class CloudFlareFormCubit extends FormCubit {
|
|||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
ValidationModel<String>(
|
||||
regExp.hasMatch, 'validations.key_format'.tr(),),
|
||||
regExp.hasMatch,
|
||||
'validations.key_format'.tr(),
|
||||
),
|
||||
LengthStringNotEqualValidation(40)
|
||||
],
|
||||
);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
|
@ -16,7 +14,9 @@ class HetznerFormCubit extends FormCubit {
|
|||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
ValidationModel<String>(
|
||||
regExp.hasMatch, 'validations.key_format'.tr(),),
|
||||
regExp.hasMatch,
|
||||
'validations.key_format'.tr(),
|
||||
),
|
||||
LengthStringNotEqualValidation(64)
|
||||
],
|
||||
);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
|
@ -9,7 +7,9 @@ import 'package:selfprivacy/logic/models/hive/user.dart';
|
|||
|
||||
class RootUserFormCubit extends FormCubit {
|
||||
RootUserFormCubit(
|
||||
this.serverInstallationCubit, final FieldCubitFactory fieldFactory,) {
|
||||
this.serverInstallationCubit,
|
||||
final FieldCubitFactory fieldFactory,
|
||||
) {
|
||||
userName = fieldFactory.createUserLoginField();
|
||||
password = fieldFactory.createUserPasswordField();
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
|
@ -7,8 +5,11 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_
|
|||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||
|
||||
class RecoveryDeviceFormCubit extends FormCubit {
|
||||
RecoveryDeviceFormCubit(this.installationCubit,
|
||||
final FieldCubitFactory fieldFactory, this.recoveryMethod,) {
|
||||
RecoveryDeviceFormCubit(
|
||||
this.installationCubit,
|
||||
final FieldCubitFactory fieldFactory,
|
||||
this.recoveryMethod,
|
||||
) {
|
||||
tokenField = fieldFactory.createRequiredStringField();
|
||||
|
||||
super.addFields([tokenField]);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
|
@ -10,7 +8,9 @@ import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart
|
|||
|
||||
class RecoveryDomainFormCubit extends FormCubit {
|
||||
RecoveryDomainFormCubit(
|
||||
this.initializingCubit, final FieldCubitFactory fieldFactory,) {
|
||||
this.initializingCubit,
|
||||
final FieldCubitFactory fieldFactory,
|
||||
) {
|
||||
serverDomainField = fieldFactory.createRequiredStringField();
|
||||
|
||||
super.addFields([serverDomainField]);
|
||||
|
@ -25,9 +25,10 @@ class RecoveryDomainFormCubit extends FormCubit {
|
|||
@override
|
||||
FutureOr<bool> asyncValidation() async {
|
||||
final ServerApi api = ServerApi(
|
||||
hasLogger: false,
|
||||
isWithToken: false,
|
||||
overrideDomain: serverDomainField.state.value,);
|
||||
hasLogger: false,
|
||||
isWithToken: false,
|
||||
overrideDomain: serverDomainField.state.value,
|
||||
);
|
||||
|
||||
// API version doesn't require access token,
|
||||
// so if the entered domain is indeed valid
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
|
@ -14,21 +12,26 @@ class SshFormCubit extends FormCubit {
|
|||
required this.user,
|
||||
}) {
|
||||
final RegExp keyRegExp = RegExp(
|
||||
r'^(ssh-rsa AAAAB3NzaC1yc2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5)[0-9A-Za-z+/]+[=]{0,3}( .*)?$',);
|
||||
r'^(ssh-rsa AAAAB3NzaC1yc2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5)[0-9A-Za-z+/]+[=]{0,3}( .*)?$',
|
||||
);
|
||||
|
||||
key = FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
ValidationModel(
|
||||
(final String newKey) => user.sshKeys.any((final String key) => key == newKey),
|
||||
(final String newKey) =>
|
||||
user.sshKeys.any((final String key) => key == newKey),
|
||||
'validations.key_already_exists'.tr(),
|
||||
),
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
ValidationModel<String>((final String s) {
|
||||
print(s);
|
||||
print(keyRegExp.hasMatch(s));
|
||||
return !keyRegExp.hasMatch(s);
|
||||
}, 'validations.invalid_format'.tr(),),
|
||||
ValidationModel<String>(
|
||||
(final String s) {
|
||||
print(s);
|
||||
print(keyRegExp.hasMatch(s));
|
||||
return !keyRegExp.hasMatch(s);
|
||||
},
|
||||
'validations.invalid_format'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
|
@ -21,7 +19,8 @@ class UserFormCubit extends FormCubit {
|
|||
login.setValue(isEdit ? user.login : '');
|
||||
password = fieldFactory.createUserPasswordField();
|
||||
password.setValue(
|
||||
isEdit ? (user.password ?? '') : StringGenerators.userPassword(),);
|
||||
isEdit ? (user.password ?? '') : StringGenerators.userPassword(),
|
||||
);
|
||||
|
||||
super.addFields([login, password]);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
|
@ -9,7 +7,8 @@ abstract class LengthStringValidation extends ValidationModel<String> {
|
|||
@override
|
||||
String? check(final String val) {
|
||||
final int length = val.length;
|
||||
final String errorMessage = errorMassage.replaceAll('[]', length.toString());
|
||||
final String errorMessage =
|
||||
errorMassage.replaceAll('[]', length.toString());
|
||||
return test(val) ? errorMessage : null;
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +16,17 @@ abstract class LengthStringValidation extends ValidationModel<String> {
|
|||
class LengthStringNotEqualValidation extends LengthStringValidation {
|
||||
/// String must be equal to [length]
|
||||
LengthStringNotEqualValidation(final int length)
|
||||
: super((final n) => n.length != length,
|
||||
'validations.length_not_equal'.tr(args: [length.toString()]),);
|
||||
: super(
|
||||
(final n) => n.length != length,
|
||||
'validations.length_not_equal'.tr(args: [length.toString()]),
|
||||
);
|
||||
}
|
||||
|
||||
class LengthStringLongerValidation extends LengthStringValidation {
|
||||
/// String must be shorter than or equal to [length]
|
||||
LengthStringLongerValidation(final int length)
|
||||
: super((final n) => n.length > length,
|
||||
'validations.length_longer'.tr(args: [length.toString()]),);
|
||||
: super(
|
||||
(final n) => n.length > length,
|
||||
'validations.length_longer'.tr(args: [length.toString()]),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
||||
|
@ -52,7 +50,11 @@ class HetznerMetricsRepository {
|
|||
}
|
||||
|
||||
List<TimeSeriesData> timeSeriesSerializer(
|
||||
final Map<String, dynamic> json, final String type,) {
|
||||
final Map<String, dynamic> json,
|
||||
final String type,
|
||||
) {
|
||||
final List list = json['time_series'][type]['values'];
|
||||
return list.map((final el) => TimeSeriesData(el[0], double.parse(el[1]))).toList();
|
||||
return list
|
||||
.map((final el) => TimeSeriesData(el[0], double.parse(el[1])))
|
||||
.toList();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'hetzner_metrics_cubit.dart';
|
||||
|
||||
abstract class HetznerMetricsState extends Equatable {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -43,11 +41,12 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
if (state is JobsStateWithJobs) {
|
||||
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||
}
|
||||
final bool needToRemoveJob =
|
||||
newJobsList.any((final el) => el is ServiceToggleJob && el.type == job.type);
|
||||
final bool needToRemoveJob = newJobsList
|
||||
.any((final el) => el is ServiceToggleJob && el.type == job.type);
|
||||
if (needToRemoveJob) {
|
||||
final Job removingJob = newJobsList
|
||||
.firstWhere((final el) => el is ServiceToggleJob && el.type == job.type);
|
||||
final Job removingJob = newJobsList.firstWhere(
|
||||
(final el) => el is ServiceToggleJob && el.type == job.type,
|
||||
);
|
||||
removeJob(removingJob.id);
|
||||
} else {
|
||||
newJobsList.add(job);
|
||||
|
@ -61,7 +60,8 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
if (state is JobsStateWithJobs) {
|
||||
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||
}
|
||||
final bool isExistInJobList = newJobsList.any((final el) => el is CreateSSHKeyJob);
|
||||
final bool isExistInJobList =
|
||||
newJobsList.any((final el) => el is CreateSSHKeyJob);
|
||||
if (!isExistInJobList) {
|
||||
newJobsList.add(job);
|
||||
getIt<NavigationService>().showSnackBar('jobs.jobAdded'.tr());
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'jobs_cubit.dart';
|
||||
|
||||
abstract class JobsState extends Equatable {
|
||||
|
@ -16,7 +14,8 @@ class JobsStateWithJobs extends JobsState {
|
|||
final List<Job> jobList;
|
||||
|
||||
JobsState removeById(final String id) {
|
||||
final List<Job> newJobsList = jobList.where((final element) => element.id != id).toList();
|
||||
final List<Job> newJobsList =
|
||||
jobList.where((final element) => element.id != id).toList();
|
||||
|
||||
if (newJobsList.isEmpty) {
|
||||
return JobsStateEmpty();
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'providers_cubit.dart';
|
||||
|
||||
class ProvidersState extends Equatable {
|
||||
|
@ -7,18 +5,23 @@ class ProvidersState extends Equatable {
|
|||
|
||||
final List<ProviderModel> all;
|
||||
|
||||
ProvidersState updateElement(final ProviderModel provider, final StateType newState) {
|
||||
ProvidersState updateElement(
|
||||
final ProviderModel provider,
|
||||
final StateType newState,
|
||||
) {
|
||||
final List<ProviderModel> newList = [...all];
|
||||
final int index = newList.indexOf(provider);
|
||||
newList[index] = provider.updateState(newState);
|
||||
return ProvidersState(newList);
|
||||
}
|
||||
|
||||
List<ProviderModel> get connected =>
|
||||
all.where((final service) => service.state != StateType.uninitialized).toList();
|
||||
List<ProviderModel> get connected => all
|
||||
.where((final service) => service.state != StateType.uninitialized)
|
||||
.toList();
|
||||
|
||||
List<ProviderModel> get uninitialized =>
|
||||
all.where((final service) => service.state == StateType.uninitialized).toList();
|
||||
List<ProviderModel> get uninitialized => all
|
||||
.where((final service) => service.state == StateType.uninitialized)
|
||||
.toList();
|
||||
|
||||
bool get isFullyInitialized => uninitialized.isEmpty;
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'recovery_key_cubit.dart';
|
||||
|
||||
class RecoveryKeyState extends ServerInstallationDependendState {
|
||||
const RecoveryKeyState(this._status, this.loadingStatus);
|
||||
|
||||
const RecoveryKeyState.initial()
|
||||
: this(const RecoveryKeyStatus(exists: false, valid: false),
|
||||
LoadingStatus.refreshing,);
|
||||
: this(
|
||||
const RecoveryKeyStatus(exists: false, valid: false),
|
||||
LoadingStatus.refreshing,
|
||||
);
|
||||
|
||||
final RecoveryKeyStatus _status;
|
||||
final LoadingStatus loadingStatus;
|
||||
|
@ -23,8 +23,9 @@ class RecoveryKeyState extends ServerInstallationDependendState {
|
|||
RecoveryKeyState copyWith({
|
||||
final RecoveryKeyStatus? status,
|
||||
final LoadingStatus? loadingStatus,
|
||||
}) => RecoveryKeyState(
|
||||
status ?? _status,
|
||||
loadingStatus ?? this.loadingStatus,
|
||||
);
|
||||
}) =>
|
||||
RecoveryKeyState(
|
||||
status ?? _status,
|
||||
loadingStatus ?? this.loadingStatus,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'server_detailed_info_cubit.dart';
|
||||
|
||||
abstract class ServerDetailsState extends Equatable {
|
||||
|
@ -18,7 +16,6 @@ class ServerDetailsNotReady extends ServerDetailsState {}
|
|||
class Loading extends ServerDetailsState {}
|
||||
|
||||
class Loaded extends ServerDetailsState {
|
||||
|
||||
const Loaded({
|
||||
required this.serverInfo,
|
||||
required this.serverTimezone,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:basic_utils/basic_utils.dart';
|
||||
|
@ -29,13 +27,11 @@ import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
|||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
|
||||
class IpNotFoundException implements Exception {
|
||||
|
||||
IpNotFoundException(this.message);
|
||||
final String message;
|
||||
}
|
||||
|
||||
class ServerAuthorizationException implements Exception {
|
||||
|
||||
ServerAuthorizationException(this.message);
|
||||
final String message;
|
||||
}
|
||||
|
@ -47,8 +43,10 @@ class ServerInstallationRepository {
|
|||
final String? hetznerToken = getIt<ApiConfigModel>().hetznerKey;
|
||||
final String? cloudflareToken = getIt<ApiConfigModel>().cloudFlareKey;
|
||||
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
|
||||
final BackblazeCredential? backblazeCredential = getIt<ApiConfigModel>().backblazeCredential;
|
||||
final ServerHostingDetails? serverDetails = getIt<ApiConfigModel>().serverDetails;
|
||||
final BackblazeCredential? backblazeCredential =
|
||||
getIt<ApiConfigModel>().backblazeCredential;
|
||||
final ServerHostingDetails? serverDetails =
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
|
||||
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
|
||||
return ServerInstallationFinished(
|
||||
|
@ -76,7 +74,11 @@ class ServerInstallationRepository {
|
|||
serverDetails: serverDetails,
|
||||
rootUser: box.get(BNames.rootUser),
|
||||
currentStep: _getCurrentRecoveryStep(
|
||||
hetznerToken, cloudflareToken, serverDomain, serverDetails,),
|
||||
hetznerToken,
|
||||
cloudflareToken,
|
||||
serverDomain,
|
||||
serverDetails,
|
||||
),
|
||||
recoveryCapabilities: await getRecoveryCapabilities(serverDomain),
|
||||
);
|
||||
}
|
||||
|
@ -146,8 +148,11 @@ class ServerInstallationRepository {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Map<String, bool>> isDnsAddressesMatch(final String? domainName, final String? ip4,
|
||||
final Map<String, bool>? skippedMatches,) async {
|
||||
Future<Map<String, bool>> isDnsAddressesMatch(
|
||||
final String? domainName,
|
||||
final String? ip4,
|
||||
final Map<String, bool>? skippedMatches,
|
||||
) async {
|
||||
final List<String> addresses = <String>[
|
||||
'$domainName',
|
||||
'api.$domainName',
|
||||
|
@ -228,9 +233,11 @@ class ServerInstallationRepository {
|
|||
isRed: true,
|
||||
onPressed: () async {
|
||||
await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||
domainName: domainName,);
|
||||
domainName: domainName,
|
||||
);
|
||||
|
||||
final ServerHostingDetails serverDetails = await hetznerApi.createServer(
|
||||
final ServerHostingDetails serverDetails =
|
||||
await hetznerApi.createServer(
|
||||
cloudFlareKey: cloudFlareKey,
|
||||
rootUser: rootUser,
|
||||
domainName: domainName,
|
||||
|
@ -284,7 +291,8 @@ class ServerInstallationRepository {
|
|||
isRed: true,
|
||||
onPressed: () async {
|
||||
await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||
domainName: cloudFlareDomain.domainName,);
|
||||
domainName: cloudFlareDomain.domainName,
|
||||
);
|
||||
|
||||
onCancel();
|
||||
},
|
||||
|
@ -358,8 +366,10 @@ class ServerInstallationRepository {
|
|||
|
||||
Future<String> getServerIpFromDomain(final ServerDomain serverDomain) async {
|
||||
final List<RRecord>? lookup = await DnsUtils.lookupRecord(
|
||||
serverDomain.domainName, RRecordType.A,
|
||||
provider: DnsApiProvider.CLOUDFLARE,);
|
||||
serverDomain.domainName,
|
||||
RRecordType.A,
|
||||
provider: DnsApiProvider.CLOUDFLARE,
|
||||
);
|
||||
if (lookup == null || lookup.isEmpty) {
|
||||
throw IpNotFoundException('No IP found for domain $serverDomain');
|
||||
}
|
||||
|
@ -369,22 +379,32 @@ class ServerInstallationRepository {
|
|||
Future<String> getDeviceName() async {
|
||||
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
if (kIsWeb) {
|
||||
return deviceInfo.webBrowserInfo
|
||||
.then((final WebBrowserInfo value) => '${value.browserName} ${value.platform}');
|
||||
return deviceInfo.webBrowserInfo.then(
|
||||
(final WebBrowserInfo value) =>
|
||||
'${value.browserName} ${value.platform}',
|
||||
);
|
||||
} else {
|
||||
if (Platform.isAndroid) {
|
||||
return deviceInfo.androidInfo
|
||||
.then((final AndroidDeviceInfo value) => '${value.model} ${value.version.release}');
|
||||
return deviceInfo.androidInfo.then(
|
||||
(final AndroidDeviceInfo value) =>
|
||||
'${value.model} ${value.version.release}',
|
||||
);
|
||||
} else if (Platform.isIOS) {
|
||||
return deviceInfo.iosInfo.then((final IosDeviceInfo value) =>
|
||||
'${value.utsname.machine} ${value.systemName} ${value.systemVersion}',);
|
||||
return deviceInfo.iosInfo.then(
|
||||
(final IosDeviceInfo value) =>
|
||||
'${value.utsname.machine} ${value.systemName} ${value.systemVersion}',
|
||||
);
|
||||
} else if (Platform.isLinux) {
|
||||
return deviceInfo.linuxInfo.then((final LinuxDeviceInfo value) => value.prettyName);
|
||||
return deviceInfo.linuxInfo
|
||||
.then((final LinuxDeviceInfo value) => value.prettyName);
|
||||
} else if (Platform.isMacOS) {
|
||||
return deviceInfo.macOsInfo
|
||||
.then((final MacOsDeviceInfo value) => '${value.hostName} ${value.computerName}');
|
||||
return deviceInfo.macOsInfo.then(
|
||||
(final MacOsDeviceInfo value) =>
|
||||
'${value.hostName} ${value.computerName}',
|
||||
);
|
||||
} else if (Platform.isWindows) {
|
||||
return deviceInfo.windowsInfo.then((final WindowsDeviceInfo value) => value.computerName);
|
||||
return deviceInfo.windowsInfo
|
||||
.then((final WindowsDeviceInfo value) => value.computerName);
|
||||
}
|
||||
}
|
||||
return 'Unidentified';
|
||||
|
@ -401,7 +421,8 @@ class ServerInstallationRepository {
|
|||
);
|
||||
final String serverIp = await getServerIpFromDomain(serverDomain);
|
||||
final ApiResponse<String> apiResponse = await serverApi.authorizeDevice(
|
||||
DeviceToken(device: await getDeviceName(), token: newDeviceKey),);
|
||||
DeviceToken(device: await getDeviceName(), token: newDeviceKey),
|
||||
);
|
||||
|
||||
if (apiResponse.isSuccess) {
|
||||
return ServerHostingDetails(
|
||||
|
@ -434,7 +455,8 @@ class ServerInstallationRepository {
|
|||
);
|
||||
final String serverIp = await getServerIpFromDomain(serverDomain);
|
||||
final ApiResponse<String> apiResponse = await serverApi.useRecoveryToken(
|
||||
DeviceToken(device: await getDeviceName(), token: recoveryKey),);
|
||||
DeviceToken(device: await getDeviceName(), token: recoveryKey),
|
||||
);
|
||||
|
||||
if (apiResponse.isSuccess) {
|
||||
return ServerHostingDetails(
|
||||
|
@ -468,7 +490,8 @@ class ServerInstallationRepository {
|
|||
);
|
||||
final String serverIp = await getServerIpFromDomain(serverDomain);
|
||||
if (recoveryCapabilities == ServerRecoveryCapabilities.legacy) {
|
||||
final Map<ServiceTypes, bool> apiResponse = await serverApi.servicesPowerCheck();
|
||||
final Map<ServiceTypes, bool> apiResponse =
|
||||
await serverApi.servicesPowerCheck();
|
||||
if (apiResponse.isNotEmpty) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: apiToken,
|
||||
|
@ -488,9 +511,11 @@ class ServerInstallationRepository {
|
|||
);
|
||||
}
|
||||
}
|
||||
final ApiResponse<String> deviceAuthKey = await serverApi.createDeviceToken();
|
||||
final ApiResponse<String> deviceAuthKey =
|
||||
await serverApi.createDeviceToken();
|
||||
final ApiResponse<String> apiResponse = await serverApi.authorizeDevice(
|
||||
DeviceToken(device: await getDeviceName(), token: deviceAuthKey.data),);
|
||||
DeviceToken(device: await getDeviceName(), token: deviceAuthKey.data),
|
||||
);
|
||||
|
||||
if (apiResponse.isSuccess) {
|
||||
return ServerHostingDetails(
|
||||
|
@ -522,7 +547,8 @@ class ServerInstallationRepository {
|
|||
);
|
||||
|
||||
final String? serverApiVersion = await serverApi.getApiVersion();
|
||||
final ApiResponse<List<String>> users = await serverApi.getUsersList(withMainUser: true);
|
||||
final ApiResponse<List<String>> users =
|
||||
await serverApi.getUsersList(withMainUser: true);
|
||||
if (serverApiVersion == null || !users.isSuccess) {
|
||||
return fallbackUser;
|
||||
}
|
||||
|
@ -544,18 +570,22 @@ class ServerInstallationRepository {
|
|||
final HetznerApi hetznerApi = HetznerApi();
|
||||
final List<HetznerServerInfo> servers = await hetznerApi.getServers();
|
||||
return servers
|
||||
.map((final HetznerServerInfo server) => ServerBasicInfo(
|
||||
id: server.id,
|
||||
name: server.name,
|
||||
ip: server.publicNet.ipv4.ip,
|
||||
reverseDns: server.publicNet.ipv4.reverseDns,
|
||||
created: server.created,
|
||||
volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0,
|
||||
),)
|
||||
.map(
|
||||
(final HetznerServerInfo server) => ServerBasicInfo(
|
||||
id: server.id,
|
||||
name: server.name,
|
||||
ip: server.publicNet.ipv4.ip,
|
||||
reverseDns: server.publicNet.ipv4.reverseDns,
|
||||
created: server.created,
|
||||
volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> saveServerDetails(final ServerHostingDetails serverDetails) async {
|
||||
Future<void> saveServerDetails(
|
||||
final ServerHostingDetails serverDetails,
|
||||
) async {
|
||||
await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
|
||||
}
|
||||
|
||||
|
@ -569,7 +599,9 @@ class ServerInstallationRepository {
|
|||
getIt<ApiConfigModel>().init();
|
||||
}
|
||||
|
||||
Future<void> saveBackblazeKey(final BackblazeCredential backblazeCredential) async {
|
||||
Future<void> saveBackblazeKey(
|
||||
final BackblazeCredential backblazeCredential,
|
||||
) async {
|
||||
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of '../server_installation/server_installation_cubit.dart';
|
||||
|
||||
abstract class ServerInstallationState extends Equatable {
|
||||
|
@ -45,8 +43,8 @@ abstract class ServerInstallationState extends Equatable {
|
|||
bool get isServerCreated => serverDetails != null;
|
||||
|
||||
bool get isFullyInitilized => _fulfilementList.every((final el) => el!);
|
||||
ServerSetupProgress get progress =>
|
||||
ServerSetupProgress.values[_fulfilementList.where((final el) => el!).length];
|
||||
ServerSetupProgress get progress => ServerSetupProgress
|
||||
.values[_fulfilementList.where((final el) => el!).length];
|
||||
|
||||
int get porgressBar {
|
||||
if (progress.index < 6) {
|
||||
|
@ -120,7 +118,6 @@ enum ServerSetupProgress {
|
|||
}
|
||||
|
||||
class ServerInstallationNotFinished extends ServerInstallationState {
|
||||
|
||||
const ServerInstallationNotFinished({
|
||||
required final super.isServerStarted,
|
||||
required final super.isServerResetedFirstTime,
|
||||
|
@ -260,7 +257,6 @@ enum ServerRecoveryMethods {
|
|||
}
|
||||
|
||||
class ServerInstallationRecovery extends ServerInstallationState {
|
||||
|
||||
const ServerInstallationRecovery({
|
||||
required this.currentStep,
|
||||
required this.recoveryCapabilities,
|
||||
|
@ -313,14 +309,14 @@ class ServerInstallationRecovery extends ServerInstallationState {
|
|||
);
|
||||
|
||||
ServerInstallationFinished finish() => ServerInstallationFinished(
|
||||
hetznerKey: hetznerKey!,
|
||||
cloudFlareKey: cloudFlareKey!,
|
||||
backblazeCredential: backblazeCredential!,
|
||||
serverDomain: serverDomain!,
|
||||
rootUser: rootUser!,
|
||||
serverDetails: serverDetails!,
|
||||
isServerStarted: true,
|
||||
isServerResetedFirstTime: true,
|
||||
isServerResetedSecondTime: true,
|
||||
);
|
||||
hetznerKey: hetznerKey!,
|
||||
cloudFlareKey: cloudFlareKey!,
|
||||
backblazeCredential: backblazeCredential!,
|
||||
serverDomain: serverDomain!,
|
||||
rootUser: rootUser!,
|
||||
serverDetails: serverDetails!,
|
||||
isServerStarted: true,
|
||||
isServerResetedFirstTime: true,
|
||||
isServerResetedSecondTime: true,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'services_cubit.dart';
|
||||
|
||||
class ServicesState extends ServerInstallationDependendState {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
|
@ -14,9 +12,13 @@ part 'users_state.dart';
|
|||
class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
||||
UsersCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(
|
||||
serverInstallationCubit,
|
||||
const UsersState(
|
||||
<User>[], User(login: 'root'), User(login: 'loading...'),),);
|
||||
serverInstallationCubit,
|
||||
const UsersState(
|
||||
<User>[],
|
||||
User(login: 'root'),
|
||||
User(login: 'loading...'),
|
||||
),
|
||||
);
|
||||
Box<User> box = Hive.box<User>(BNames.usersBox);
|
||||
Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
||||
|
||||
|
@ -26,22 +28,35 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final List<User> loadedUsers = box.values.toList();
|
||||
final primaryUser = serverInstallationBox.get(BNames.rootUser,
|
||||
defaultValue: const User(login: 'loading...'),);
|
||||
final primaryUser = serverInstallationBox.get(
|
||||
BNames.rootUser,
|
||||
defaultValue: const User(login: 'loading...'),
|
||||
);
|
||||
final List<String> rootKeys = [
|
||||
...serverInstallationBox.get(BNames.rootKeys, defaultValue: [])
|
||||
];
|
||||
if (loadedUsers.isNotEmpty) {
|
||||
emit(UsersState(
|
||||
loadedUsers, User(login: 'root', sshKeys: rootKeys), primaryUser,),);
|
||||
emit(
|
||||
UsersState(
|
||||
loadedUsers,
|
||||
User(login: 'root', sshKeys: rootKeys),
|
||||
primaryUser,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final ApiResponse<List<String>> usersFromServer = await api.getUsersList();
|
||||
final ApiResponse<List<String>> usersFromServer =
|
||||
await api.getUsersList();
|
||||
if (usersFromServer.isSuccess) {
|
||||
final List<User> updatedList =
|
||||
mergeLocalAndServerUsers(loadedUsers, usersFromServer.data);
|
||||
emit(UsersState(
|
||||
updatedList, User(login: 'root', sshKeys: rootKeys), primaryUser,),);
|
||||
emit(
|
||||
UsersState(
|
||||
updatedList,
|
||||
User(login: 'root', sshKeys: rootKeys),
|
||||
primaryUser,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final List<User> usersWithSshKeys = await loadSshKeys(state.users);
|
||||
|
@ -49,18 +64,26 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
box.clear();
|
||||
box.addAll(usersWithSshKeys);
|
||||
|
||||
final User rootUserWithSshKeys = (await loadSshKeys([state.rootUser])).first;
|
||||
final User rootUserWithSshKeys =
|
||||
(await loadSshKeys([state.rootUser])).first;
|
||||
serverInstallationBox.put(BNames.rootKeys, rootUserWithSshKeys.sshKeys);
|
||||
final User primaryUserWithSshKeys =
|
||||
(await loadSshKeys([state.primaryUser])).first;
|
||||
serverInstallationBox.put(BNames.rootUser, primaryUserWithSshKeys);
|
||||
emit(UsersState(
|
||||
usersWithSshKeys, rootUserWithSshKeys, primaryUserWithSshKeys,),);
|
||||
emit(
|
||||
UsersState(
|
||||
usersWithSshKeys,
|
||||
rootUserWithSshKeys,
|
||||
primaryUserWithSshKeys,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<User> mergeLocalAndServerUsers(
|
||||
final List<User> localUsers, final List<String> serverUsers,) {
|
||||
final List<User> localUsers,
|
||||
final List<String> serverUsers,
|
||||
) {
|
||||
// If local user not exists on server, add it with isFoundOnServer = false
|
||||
// If server user not exists on local, add it
|
||||
|
||||
|
@ -69,28 +92,34 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
|
||||
for (final User localUser in localUsers) {
|
||||
if (serverUsersCopy.contains(localUser.login)) {
|
||||
mergedUsers.add(User(
|
||||
login: localUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: localUser.password,
|
||||
sshKeys: localUser.sshKeys,
|
||||
),);
|
||||
mergedUsers.add(
|
||||
User(
|
||||
login: localUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: localUser.password,
|
||||
sshKeys: localUser.sshKeys,
|
||||
),
|
||||
);
|
||||
serverUsersCopy.remove(localUser.login);
|
||||
} else {
|
||||
mergedUsers.add(User(
|
||||
login: localUser.login,
|
||||
isFoundOnServer: false,
|
||||
password: localUser.password,
|
||||
note: localUser.note,
|
||||
),);
|
||||
mergedUsers.add(
|
||||
User(
|
||||
login: localUser.login,
|
||||
isFoundOnServer: false,
|
||||
password: localUser.password,
|
||||
note: localUser.note,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (final String serverUser in serverUsersCopy) {
|
||||
mergedUsers.add(User(
|
||||
login: serverUser,
|
||||
isFoundOnServer: true,
|
||||
),);
|
||||
mergedUsers.add(
|
||||
User(
|
||||
login: serverUser,
|
||||
isFoundOnServer: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return mergedUsers;
|
||||
|
@ -103,31 +132,38 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
if (user.isFoundOnServer ||
|
||||
user.login == 'root' ||
|
||||
user.login == state.primaryUser.login) {
|
||||
final ApiResponse<List<String>> sshKeys = await api.getUserSshKeys(user);
|
||||
final ApiResponse<List<String>> sshKeys =
|
||||
await api.getUserSshKeys(user);
|
||||
print('sshKeys for $user: ${sshKeys.data}');
|
||||
if (sshKeys.isSuccess) {
|
||||
updatedUsers.add(User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
sshKeys: sshKeys.data,
|
||||
note: user.note,
|
||||
),);
|
||||
updatedUsers.add(
|
||||
User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
sshKeys: sshKeys.data,
|
||||
note: user.note,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
updatedUsers.add(User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
note: user.note,
|
||||
),);
|
||||
updatedUsers.add(
|
||||
User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
note: user.note,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
updatedUsers.add(User(
|
||||
login: user.login,
|
||||
isFoundOnServer: false,
|
||||
password: user.password,
|
||||
note: user.note,
|
||||
),);
|
||||
updatedUsers.add(
|
||||
User(
|
||||
login: user.login,
|
||||
isFoundOnServer: false,
|
||||
password: user.password,
|
||||
note: user.note,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return updatedUsers;
|
||||
|
@ -143,19 +179,26 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
final List<User> usersWithSshKeys = await loadSshKeys(updatedUsers);
|
||||
box.clear();
|
||||
box.addAll(usersWithSshKeys);
|
||||
final User rootUserWithSshKeys = (await loadSshKeys([state.rootUser])).first;
|
||||
final User rootUserWithSshKeys =
|
||||
(await loadSshKeys([state.rootUser])).first;
|
||||
serverInstallationBox.put(BNames.rootKeys, rootUserWithSshKeys.sshKeys);
|
||||
final User primaryUserWithSshKeys =
|
||||
(await loadSshKeys([state.primaryUser])).first;
|
||||
serverInstallationBox.put(BNames.rootUser, primaryUserWithSshKeys);
|
||||
emit(UsersState(
|
||||
usersWithSshKeys, rootUserWithSshKeys, primaryUserWithSshKeys,),);
|
||||
emit(
|
||||
UsersState(
|
||||
usersWithSshKeys,
|
||||
rootUserWithSshKeys,
|
||||
primaryUserWithSshKeys,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> createUser(final User user) async {
|
||||
// If user exists on server, do nothing
|
||||
if (state.users.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
||||
if (state.users
|
||||
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
||||
return;
|
||||
}
|
||||
// If user is root or primary user, do nothing
|
||||
|
@ -201,15 +244,17 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
.get(BNames.rootKeys, defaultValue: []) as List<String>;
|
||||
rootKeys.add(publicKey);
|
||||
serverInstallationBox.put(BNames.rootKeys, rootKeys);
|
||||
emit(state.copyWith(
|
||||
rootUser: User(
|
||||
login: state.rootUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.rootUser.password,
|
||||
sshKeys: rootKeys,
|
||||
note: state.rootUser.note,
|
||||
emit(
|
||||
state.copyWith(
|
||||
rootUser: User(
|
||||
login: state.rootUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.rootUser.password,
|
||||
sshKeys: rootKeys,
|
||||
note: state.rootUser.note,
|
||||
),
|
||||
),
|
||||
),);
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final ApiResponse<void> result = await api.addUserSshKey(user, publicKey);
|
||||
|
@ -227,9 +272,11 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
note: state.primaryUser.note,
|
||||
);
|
||||
serverInstallationBox.put(BNames.rootUser, updatedUser);
|
||||
emit(state.copyWith(
|
||||
primaryUser: updatedUser,
|
||||
),);
|
||||
emit(
|
||||
state.copyWith(
|
||||
primaryUser: updatedUser,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// If it is not primary user, update user
|
||||
final List<String> userKeys = List<String>.from(user.sshKeys);
|
||||
|
@ -242,9 +289,11 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
note: user.note,
|
||||
);
|
||||
await box.putAt(box.values.toList().indexOf(user), updatedUser);
|
||||
emit(state.copyWith(
|
||||
users: box.values.toList(),
|
||||
),);
|
||||
emit(
|
||||
state.copyWith(
|
||||
users: box.values.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -253,7 +302,8 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
Future<void> deleteSshKey(final User user, final String publicKey) async {
|
||||
// All keys are deleted via api.deleteUserSshKey
|
||||
|
||||
final ApiResponse<void> result = await api.deleteUserSshKey(user, publicKey);
|
||||
final ApiResponse<void> result =
|
||||
await api.deleteUserSshKey(user, publicKey);
|
||||
if (result.isSuccess) {
|
||||
// If it is root user, delete key from root keys
|
||||
// If it is primary user, update primary user
|
||||
|
@ -264,15 +314,17 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
.get(BNames.rootKeys, defaultValue: []) as List<String>;
|
||||
rootKeys.remove(publicKey);
|
||||
serverInstallationBox.put(BNames.rootKeys, rootKeys);
|
||||
emit(state.copyWith(
|
||||
rootUser: User(
|
||||
login: state.rootUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.rootUser.password,
|
||||
sshKeys: rootKeys,
|
||||
note: state.rootUser.note,
|
||||
emit(
|
||||
state.copyWith(
|
||||
rootUser: User(
|
||||
login: state.rootUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.rootUser.password,
|
||||
sshKeys: rootKeys,
|
||||
note: state.rootUser.note,
|
||||
),
|
||||
),
|
||||
),);
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (user.login == state.primaryUser.login) {
|
||||
|
@ -287,9 +339,11 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
note: state.primaryUser.note,
|
||||
);
|
||||
serverInstallationBox.put(BNames.rootUser, updatedUser);
|
||||
emit(state.copyWith(
|
||||
primaryUser: updatedUser,
|
||||
),);
|
||||
emit(
|
||||
state.copyWith(
|
||||
primaryUser: updatedUser,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
final List<String> userKeys = List<String>.from(user.sshKeys);
|
||||
|
@ -302,15 +356,22 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|||
note: user.note,
|
||||
);
|
||||
await box.putAt(box.values.toList().indexOf(user), updatedUser);
|
||||
emit(state.copyWith(
|
||||
users: box.values.toList(),
|
||||
),);
|
||||
emit(
|
||||
state.copyWith(
|
||||
users: box.values.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() async {
|
||||
emit(const UsersState(
|
||||
<User>[], User(login: 'root'), User(login: 'loading...'),),);
|
||||
emit(
|
||||
const UsersState(
|
||||
<User>[],
|
||||
User(login: 'root'),
|
||||
User(login: 'loading...'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'users_cubit.dart';
|
||||
|
||||
class UsersState extends ServerInstallationDependendState {
|
||||
|
@ -16,15 +14,17 @@ class UsersState extends ServerInstallationDependendState {
|
|||
final List<User>? users,
|
||||
final User? rootUser,
|
||||
final User? primaryUser,
|
||||
}) => UsersState(
|
||||
users ?? this.users,
|
||||
rootUser ?? this.rootUser,
|
||||
primaryUser ?? this.primaryUser,
|
||||
);
|
||||
}) =>
|
||||
UsersState(
|
||||
users ?? this.users,
|
||||
rootUser ?? this.rootUser,
|
||||
primaryUser ?? this.primaryUser,
|
||||
);
|
||||
|
||||
bool isLoginRegistered(final String login) => users.any((final User user) => user.login == login) ||
|
||||
login == rootUser.login ||
|
||||
login == primaryUser.login;
|
||||
bool isLoginRegistered(final String login) =>
|
||||
users.any((final User user) => user.login == login) ||
|
||||
login == rootUser.login ||
|
||||
login == primaryUser.login;
|
||||
|
||||
bool get isEmpty => users.isEmpty;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/models/message.dart';
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
@ -39,5 +37,6 @@ class User extends Equatable {
|
|||
Color get color => stringToColor(login);
|
||||
|
||||
@override
|
||||
String toString() => '$login, ${isFoundOnServer ? 'found' : 'not found'}, ${sshKeys.length} ssh keys, note: $note';
|
||||
String toString() =>
|
||||
'$login, ${isFoundOnServer ? 'found' : 'not found'}, ${sshKeys.length} ssh keys, note: $note';
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
// ignore_for_file: always_specify_types
|
||||
|
||||
part of 'user.dart';
|
||||
|
||||
// **************************************************************************
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'hetzner_server_info.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class HetznerServerInfo {
|
||||
|
||||
HetznerServerInfo(
|
||||
this.id,
|
||||
this.name,
|
||||
|
@ -41,7 +38,6 @@ class HetznerServerInfo {
|
|||
|
||||
@JsonSerializable()
|
||||
class HetznerPublicNetInfo {
|
||||
|
||||
HetznerPublicNetInfo(this.ipv4);
|
||||
final HetznerIp4 ipv4;
|
||||
|
||||
|
@ -51,7 +47,6 @@ class HetznerPublicNetInfo {
|
|||
|
||||
@JsonSerializable()
|
||||
class HetznerIp4 {
|
||||
|
||||
HetznerIp4(this.id, this.ip, this.blocked, this.reverseDns);
|
||||
final bool blocked;
|
||||
@JsonKey(name: 'dns_ptr')
|
||||
|
@ -77,7 +72,6 @@ enum ServerStatus {
|
|||
|
||||
@JsonSerializable()
|
||||
class HetznerServerTypeInfo {
|
||||
|
||||
HetznerServerTypeInfo(this.cores, this.memory, this.disk, this.prices);
|
||||
final int cores;
|
||||
final num memory;
|
||||
|
@ -102,12 +96,12 @@ class HetznerPriceInfo {
|
|||
static HetznerPriceInfo fromJson(final Map<String, dynamic> json) =>
|
||||
_$HetznerPriceInfoFromJson(json);
|
||||
|
||||
static double getPrice(final Map json) => double.parse(json['gross'] as String);
|
||||
static double getPrice(final Map json) =>
|
||||
double.parse(json['gross'] as String);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class HetznerLocation {
|
||||
|
||||
HetznerLocation(this.country, this.city, this.description, this.zone);
|
||||
final String country;
|
||||
final String city;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
|
@ -7,7 +5,6 @@ part 'recovery_token_status.g.dart';
|
|||
|
||||
@JsonSerializable()
|
||||
class RecoveryKeyStatus extends Equatable {
|
||||
|
||||
factory RecoveryKeyStatus.fromJson(final Map<String, dynamic> json) =>
|
||||
_$RecoveryKeyStatusFromJson(json);
|
||||
const RecoveryKeyStatus({
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
|
@ -7,7 +5,6 @@ part 'server_configurations.g.dart';
|
|||
|
||||
@JsonSerializable(createToJson: true)
|
||||
class AutoUpgradeConfigurations extends Equatable {
|
||||
|
||||
factory AutoUpgradeConfigurations.fromJson(final Map<String, dynamic> json) =>
|
||||
_$AutoUpgradeConfigurationsFromJson(json);
|
||||
const AutoUpgradeConfigurations({
|
||||
|
|
|
@ -4,16 +4,14 @@ final DateFormat formatter = DateFormat('hh:mm');
|
|||
|
||||
class Message {
|
||||
Message({this.text, this.type = MessageType.normal}) : time = DateTime.now();
|
||||
Message.warn({this.text})
|
||||
: type = MessageType.warning,
|
||||
time = DateTime.now();
|
||||
|
||||
final String? text;
|
||||
final DateTime time;
|
||||
final MessageType type;
|
||||
String get timeString => formatter.format(time);
|
||||
|
||||
static Message warn({final String? text}) => Message(
|
||||
text: text,
|
||||
type: MessageType.warning,
|
||||
);
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
class ServerBasicInfo {
|
||||
|
||||
ServerBasicInfo({
|
||||
required this.id,
|
||||
required this.name,
|
||||
|
@ -19,7 +16,6 @@ class ServerBasicInfo {
|
|||
}
|
||||
|
||||
class ServerBasicInfoWithValidators extends ServerBasicInfo {
|
||||
|
||||
ServerBasicInfoWithValidators.fromServerBasicInfo({
|
||||
required final ServerBasicInfo serverBasicInfo,
|
||||
required final isIpValid,
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:timezone/timezone.dart';
|
||||
|
||||
class TimeZoneSettings {
|
||||
|
||||
factory TimeZoneSettings.fromString(final String string) {
|
||||
final Location location = timeZoneDatabase.locations[string]!;
|
||||
return TimeZoneSettings(location);
|
||||
|
@ -13,6 +10,6 @@ class TimeZoneSettings {
|
|||
final Location timezone;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'timezone': timezone.name,
|
||||
};
|
||||
'timezone': timezone.name,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -46,11 +44,14 @@ void main() async {
|
|||
);
|
||||
|
||||
BlocOverrides.runZoned(
|
||||
() => runApp(Localization(
|
||||
() => runApp(
|
||||
Localization(
|
||||
child: MyApp(
|
||||
lightThemeData: lightThemeData,
|
||||
darkThemeData: darkThemeData,
|
||||
),),),
|
||||
lightThemeData: lightThemeData,
|
||||
darkThemeData: darkThemeData,
|
||||
),
|
||||
),
|
||||
),
|
||||
blocObserver: SimpleBlocObserver(),
|
||||
);
|
||||
}
|
||||
|
@ -67,11 +68,15 @@ class MyApp extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Localization(
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light, // Manually changing appbar color
|
||||
child: BlocAndProviderConfig(
|
||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
||||
builder: (final BuildContext context, final AppSettingsState appSettings) => MaterialApp(
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light, // Manually changing appbar color
|
||||
child: BlocAndProviderConfig(
|
||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
||||
builder: (
|
||||
final BuildContext context,
|
||||
final AppSettingsState appSettings,
|
||||
) =>
|
||||
MaterialApp(
|
||||
scaffoldMessengerKey:
|
||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||
navigatorKey: getIt.get<NavigationService>().navigatorKey,
|
||||
|
@ -97,8 +102,8 @@ class MyApp extends StatelessWidget {
|
|||
return widget!;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:material_color_utilities/palettes/core_palette.dart';
|
||||
import 'package:system_theme/system_theme.dart';
|
||||
import 'package:gtk_theme_fl/gtk_theme_fl.dart';
|
||||
|
||||
abstract class AppThemeFactory {
|
||||
AppThemeFactory._();
|
||||
|
||||
static Future<ThemeData> create(
|
||||
{required final bool isDark, required final Color fallbackColor,}) => _createAppTheme(
|
||||
isDark: isDark,
|
||||
fallbackColor: fallbackColor,
|
||||
);
|
||||
static Future<ThemeData> create({
|
||||
required final bool isDark,
|
||||
required final Color fallbackColor,
|
||||
}) =>
|
||||
_createAppTheme(
|
||||
isDark: isDark,
|
||||
fallbackColor: fallbackColor,
|
||||
);
|
||||
|
||||
static Future<ThemeData> _createAppTheme({
|
||||
required final Color fallbackColor,
|
||||
|
@ -25,7 +25,8 @@ abstract class AppThemeFactory {
|
|||
ColorScheme? gtkColorsScheme;
|
||||
final Brightness brightness = isDark ? Brightness.dark : Brightness.light;
|
||||
|
||||
final ColorScheme? dynamicColorsScheme = await _getDynamicColors(brightness);
|
||||
final ColorScheme? dynamicColorsScheme =
|
||||
await _getDynamicColors(brightness);
|
||||
|
||||
if (Platform.isLinux) {
|
||||
final GtkThemeData themeData = await GtkThemeData.initialize();
|
||||
|
@ -77,7 +78,8 @@ abstract class AppThemeFactory {
|
|||
static Future<ColorScheme?> _getDynamicColors(final Brightness brightness) {
|
||||
try {
|
||||
return DynamicColorPlugin.getCorePalette().then(
|
||||
(final CorePalette? corePallet) => corePallet?.toColorScheme(brightness: brightness),);
|
||||
(final corePallet) => corePallet?.toColorScheme(brightness: brightness),
|
||||
);
|
||||
} on PlatformException {
|
||||
return Future.value(null);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class ActionButton extends StatelessWidget {
|
|||
),
|
||||
onPressed: () {
|
||||
navigator.pop();
|
||||
if (onPressed != null) onPressed!();
|
||||
onPressed?.call();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
|
@ -42,13 +40,13 @@ class BrandButton {
|
|||
child: TextButton(onPressed: onPressed, child: Text(title)),
|
||||
);
|
||||
|
||||
static _IconTextButton emptyWithIconText({
|
||||
static IconTextButton emptyWithIconText({
|
||||
required final VoidCallback onPressed,
|
||||
required final String title,
|
||||
required final Icon icon,
|
||||
final Key? key,
|
||||
}) =>
|
||||
_IconTextButton(
|
||||
IconTextButton(
|
||||
key: key,
|
||||
title: title,
|
||||
onPressed: onPressed,
|
||||
|
@ -56,8 +54,13 @@ class BrandButton {
|
|||
);
|
||||
}
|
||||
|
||||
class _IconTextButton extends StatelessWidget {
|
||||
const _IconTextButton({final super.key, this.onPressed, this.title, this.icon});
|
||||
class IconTextButton extends StatelessWidget {
|
||||
const IconTextButton({
|
||||
final super.key,
|
||||
this.onPressed,
|
||||
this.title,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
final VoidCallback? onPressed;
|
||||
final String? title;
|
||||
|
@ -65,24 +68,24 @@ class _IconTextButton extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onPressed,
|
||||
child: Container(
|
||||
height: 48,
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BrandText.body1(title),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: icon,
|
||||
)
|
||||
],
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onPressed,
|
||||
child: Container(
|
||||
height: 48,
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BrandText.body1(title),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: icon,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BrandCards {
|
||||
|
@ -24,8 +22,10 @@ class BrandCards {
|
|||
static Widget outlined({required final Widget child}) => _OutlinedCard(
|
||||
child: child,
|
||||
);
|
||||
static Widget filled(
|
||||
{required final Widget child, final bool tertiary = false,}) =>
|
||||
static Widget filled({
|
||||
required final Widget child,
|
||||
final bool tertiary = false,
|
||||
}) =>
|
||||
_FilledCard(
|
||||
tertiary: tertiary,
|
||||
child: child,
|
||||
|
@ -38,7 +38,6 @@ class _BrandCard extends StatelessWidget {
|
|||
required this.padding,
|
||||
required this.shadow,
|
||||
required this.borderRadius,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
@ -60,7 +59,6 @@ class _BrandCard extends StatelessWidget {
|
|||
|
||||
class _OutlinedCard extends StatelessWidget {
|
||||
const _OutlinedCard({
|
||||
final super.key,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
|
@ -83,7 +81,6 @@ class _FilledCard extends StatelessWidget {
|
|||
const _FilledCard({
|
||||
required this.child,
|
||||
required this.tertiary,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
|
|
@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
|
|||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
|
||||
class BrandDivider extends StatelessWidget {
|
||||
const BrandDivider({super.key});
|
||||
const BrandDivider({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Container(
|
||||
width: double.infinity,
|
||||
height: 1,
|
||||
color: BrandColors.dividerColor,
|
||||
);
|
||||
width: double.infinity,
|
||||
height: 1,
|
||||
color: BrandColors.dividerColor,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: always_specify_types
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
|
@ -18,24 +16,24 @@ class BrandHeader extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Container(
|
||||
height: 52,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: EdgeInsets.only(
|
||||
left: hasBackButton ? 1 : 15,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (hasBackButton) ...[
|
||||
IconButton(
|
||||
icon: const Icon(BrandIcons.arrowLeft),
|
||||
onPressed:
|
||||
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
height: 52,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: EdgeInsets.only(
|
||||
left: hasBackButton ? 1 : 15,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (hasBackButton) ...[
|
||||
IconButton(
|
||||
icon: const Icon(BrandIcons.arrowLeft),
|
||||
onPressed:
|
||||
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
BrandText.h4(title),
|
||||
const Spacer(),
|
||||
],
|
||||
BrandText.h4(title),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,48 +25,50 @@ class BrandHeroScreen extends StatelessWidget {
|
|||
final VoidCallback? onBackButtonPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52.0),
|
||||
child: BrandHeader(
|
||||
title: headerTitle,
|
||||
hasBackButton: hasBackButton,
|
||||
onBackButtonPressed: onBackButtonPressed,
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52.0),
|
||||
child: BrandHeader(
|
||||
title: headerTitle,
|
||||
hasBackButton: hasBackButton,
|
||||
onBackButtonPressed: onBackButtonPressed,
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: hasFlashButton ? const BrandFab() : null,
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: <Widget>[
|
||||
if (heroIcon != null)
|
||||
Container(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Icon(
|
||||
heroIcon,
|
||||
size: 48.0,
|
||||
floatingActionButton: hasFlashButton ? const BrandFab() : null,
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: <Widget>[
|
||||
if (heroIcon != null)
|
||||
Container(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Icon(
|
||||
heroIcon,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
if (heroTitle != null)
|
||||
Text(
|
||||
heroTitle!,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
if (heroSubtitle != null)
|
||||
Text(heroSubtitle!,
|
||||
const SizedBox(height: 8.0),
|
||||
if (heroTitle != null)
|
||||
Text(
|
||||
heroTitle!,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
if (heroSubtitle != null)
|
||||
Text(
|
||||
heroSubtitle!,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
textAlign: TextAlign.start,),
|
||||
const SizedBox(height: 16.0),
|
||||
...children,
|
||||
],
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,17 +2,19 @@ import 'package:flutter/material.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class BrandLoader {
|
||||
static _HorizontalLoader horizontal() => _HorizontalLoader();
|
||||
static HorizontalLoader horizontal() => const HorizontalLoader();
|
||||
}
|
||||
|
||||
class _HorizontalLoader extends StatelessWidget {
|
||||
class HorizontalLoader extends StatelessWidget {
|
||||
const HorizontalLoader({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('basis.wait'.tr()),
|
||||
const SizedBox(height: 10),
|
||||
const LinearProgressIndicator(minHeight: 3),
|
||||
],
|
||||
);
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('basis.wait'.tr()),
|
||||
const SizedBox(height: 10),
|
||||
const LinearProgressIndicator(minHeight: 3),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,36 +3,36 @@ import 'package:selfprivacy/config/brand_colors.dart';
|
|||
|
||||
class BrandRadio extends StatelessWidget {
|
||||
const BrandRadio({
|
||||
super.key,
|
||||
required this.isChecked,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final bool isChecked;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: _getBorder(),
|
||||
),
|
||||
child: isChecked
|
||||
? Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: BrandColors.primary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
height: 20,
|
||||
width: 20,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: _getBorder(),
|
||||
),
|
||||
child: isChecked
|
||||
? Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: BrandColors.primary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
BoxBorder? _getBorder() => Border.all(
|
||||
color: isChecked ? BrandColors.primary : BrandColors.gray1,
|
||||
width: 2,
|
||||
);
|
||||
color: isChecked ? BrandColors.primary : BrandColors.gray1,
|
||||
width: 2,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|||
|
||||
class BrandRadioTile extends StatelessWidget {
|
||||
const BrandRadioTile({
|
||||
super.key,
|
||||
required this.isChecked,
|
||||
required this.text,
|
||||
required this.onPress,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final bool isChecked;
|
||||
|
@ -16,20 +16,20 @@ class BrandRadioTile extends StatelessWidget {
|
|||
final VoidCallback onPress;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => GestureDetector(
|
||||
onTap: onPress,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: Row(
|
||||
children: [
|
||||
BrandRadio(
|
||||
isChecked: isChecked,
|
||||
),
|
||||
const SizedBox(width: 9),
|
||||
BrandText.h5(text)
|
||||
],
|
||||
Widget build(final BuildContext context) => GestureDetector(
|
||||
onTap: onPress,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: Row(
|
||||
children: [
|
||||
BrandRadio(
|
||||
isChecked: isChecked,
|
||||
),
|
||||
const SizedBox(width: 9),
|
||||
BrandText.h5(text)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,18 +14,18 @@ class BrandSpanButton extends TextSpan {
|
|||
style: (style ?? const TextStyle()).copyWith(color: BrandColors.blue),
|
||||
);
|
||||
|
||||
static BrandSpanButton link({
|
||||
BrandSpanButton.link({
|
||||
required final String text,
|
||||
final String? urlString,
|
||||
final TextStyle? style,
|
||||
}) =>
|
||||
BrandSpanButton(
|
||||
text: text,
|
||||
style: style,
|
||||
onTap: () => _launchURL(urlString ?? text),
|
||||
);
|
||||
}) : super(
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => _launchURL(urlString ?? text),
|
||||
text: text,
|
||||
style: (style ?? const TextStyle()).copyWith(color: BrandColors.blue),
|
||||
);
|
||||
|
||||
static _launchURL(final String link) async {
|
||||
static Future<void> _launchURL(final String link) async {
|
||||
if (await canLaunchUrl(Uri.parse(link))) {
|
||||
await launchUrl(Uri.parse(link));
|
||||
} else {
|
||||
|
|
|
@ -11,9 +11,9 @@ class BrandSwitch extends StatelessWidget {
|
|||
final bool value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Switch(
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
);
|
||||
Widget build(final BuildContext context) => Switch(
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class _BrandTabBarState extends State<BrandTabBar> {
|
|||
super.initState();
|
||||
}
|
||||
|
||||
_listener() {
|
||||
void _listener() {
|
||||
if (currentIndex != widget.controller!.index) {
|
||||
setState(() {
|
||||
currentIndex = widget.controller!.index;
|
||||
|
@ -35,21 +35,26 @@ class _BrandTabBarState extends State<BrandTabBar> {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => NavigationBar(
|
||||
destinations: [
|
||||
_getIconButton('basis.providers'.tr(), BrandIcons.server, 0),
|
||||
_getIconButton('basis.services'.tr(), BrandIcons.box, 1),
|
||||
_getIconButton('basis.users'.tr(), BrandIcons.users, 2),
|
||||
_getIconButton('basis.more'.tr(), Icons.menu_rounded, 3),
|
||||
],
|
||||
onDestinationSelected: (final index) {
|
||||
widget.controller!.animateTo(index);
|
||||
},
|
||||
selectedIndex: currentIndex ?? 0,
|
||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||
);
|
||||
destinations: [
|
||||
_getIconButton('basis.providers'.tr(), BrandIcons.server, 0),
|
||||
_getIconButton('basis.services'.tr(), BrandIcons.box, 1),
|
||||
_getIconButton('basis.users'.tr(), BrandIcons.users, 2),
|
||||
_getIconButton('basis.more'.tr(), Icons.menu_rounded, 3),
|
||||
],
|
||||
onDestinationSelected: (final index) {
|
||||
widget.controller!.animateTo(index);
|
||||
},
|
||||
selectedIndex: currentIndex ?? 0,
|
||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||
);
|
||||
|
||||
NavigationDestination _getIconButton(final String label, final IconData iconData, final int index) => NavigationDestination(
|
||||
icon: Icon(iconData),
|
||||
label: label,
|
||||
);
|
||||
NavigationDestination _getIconButton(
|
||||
final String label,
|
||||
final IconData iconData,
|
||||
final int index,
|
||||
) =>
|
||||
NavigationDestination(
|
||||
icon: Icon(iconData),
|
||||
label: label,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,13 +33,20 @@ class BrandText extends StatelessWidget {
|
|||
textAlign: textAlign,
|
||||
);
|
||||
|
||||
factory BrandText.onboardingTitle(final String text, {final TextStyle? style}) =>
|
||||
factory BrandText.onboardingTitle(
|
||||
final String text, {
|
||||
final TextStyle? style,
|
||||
}) =>
|
||||
BrandText(
|
||||
text,
|
||||
type: TextType.onboardingTitle,
|
||||
style: style,
|
||||
);
|
||||
factory BrandText.h3(final String text, {final TextStyle? style, final TextAlign? textAlign}) =>
|
||||
factory BrandText.h3(
|
||||
final String text, {
|
||||
final TextStyle? style,
|
||||
final TextAlign? textAlign,
|
||||
}) =>
|
||||
BrandText(
|
||||
text,
|
||||
type: TextType.h3,
|
||||
|
@ -85,22 +92,28 @@ class BrandText extends StatelessWidget {
|
|||
style: style,
|
||||
textAlign: textAlign,
|
||||
);
|
||||
factory BrandText.body1(final String? text, {final TextStyle? style}) => BrandText(
|
||||
factory BrandText.body1(final String? text, {final TextStyle? style}) =>
|
||||
BrandText(
|
||||
text,
|
||||
type: TextType.body1,
|
||||
style: style,
|
||||
);
|
||||
factory BrandText.small(final String text, {final TextStyle? style}) => BrandText(
|
||||
factory BrandText.small(final String text, {final TextStyle? style}) =>
|
||||
BrandText(
|
||||
text,
|
||||
type: TextType.small,
|
||||
style: style,
|
||||
);
|
||||
factory BrandText.body2(final String? text, {final TextStyle? style}) => BrandText(
|
||||
factory BrandText.body2(final String? text, {final TextStyle? style}) =>
|
||||
BrandText(
|
||||
text,
|
||||
type: TextType.body2,
|
||||
style: style,
|
||||
);
|
||||
factory BrandText.buttonTitleText(final String? text, {final TextStyle? style}) =>
|
||||
factory BrandText.buttonTitleText(
|
||||
final String? text, {
|
||||
final TextStyle? style,
|
||||
}) =>
|
||||
BrandText(
|
||||
text,
|
||||
type: TextType.buttonTitleText,
|
||||
|
@ -118,8 +131,11 @@ class BrandText extends StatelessWidget {
|
|||
style: style,
|
||||
textAlign: textAlign,
|
||||
);
|
||||
factory BrandText.medium(final String? text,
|
||||
{final TextStyle? style, final TextAlign? textAlign}) =>
|
||||
factory BrandText.medium(
|
||||
final String? text, {
|
||||
final TextStyle? style,
|
||||
final TextAlign? textAlign,
|
||||
}) =>
|
||||
BrandText(
|
||||
text,
|
||||
type: TextType.medium,
|
||||
|
@ -128,9 +144,9 @@ class BrandText extends StatelessWidget {
|
|||
);
|
||||
const BrandText(
|
||||
this.text, {
|
||||
super.key,
|
||||
this.style,
|
||||
required this.type,
|
||||
final super.key,
|
||||
this.style,
|
||||
this.overflow,
|
||||
this.softWrap,
|
||||
this.textAlign,
|
||||
|
|
|
@ -7,9 +7,9 @@ import 'package:selfprivacy/utils/named_font_weight.dart';
|
|||
|
||||
class BrandTimer extends StatefulWidget {
|
||||
const BrandTimer({
|
||||
super.key,
|
||||
required this.startDateTime,
|
||||
required this.duration,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final DateTime startDateTime;
|
||||
|
@ -29,10 +29,11 @@ class _BrandTimerState extends State<BrandTimer> {
|
|||
super.initState();
|
||||
}
|
||||
|
||||
_timerStart() {
|
||||
void _timerStart() {
|
||||
_timeString = differenceFromStart;
|
||||
timer = Timer.periodic(const Duration(seconds: 1), (final Timer t) {
|
||||
final Duration timePassed = DateTime.now().difference(widget.startDateTime);
|
||||
final Duration timePassed =
|
||||
DateTime.now().difference(widget.startDateTime);
|
||||
if (timePassed > widget.duration) {
|
||||
t.cancel();
|
||||
} else {
|
||||
|
@ -52,11 +53,11 @@ class _BrandTimerState extends State<BrandTimer> {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => BrandText.medium(
|
||||
_timeString,
|
||||
style: const TextStyle(
|
||||
fontWeight: NamedFontWeight.demiBold,
|
||||
),
|
||||
);
|
||||
_timeString,
|
||||
style: const TextStyle(
|
||||
fontWeight: NamedFontWeight.demiBold,
|
||||
),
|
||||
);
|
||||
|
||||
void _getTime() {
|
||||
setState(() {
|
||||
|
|
|
@ -4,9 +4,9 @@ import 'package:selfprivacy/logic/models/state_types.dart';
|
|||
|
||||
class IconStatusMask extends StatelessWidget {
|
||||
const IconStatusMask({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.status,
|
||||
final super.key,
|
||||
});
|
||||
final Icon child;
|
||||
|
||||
|
|
|
@ -13,106 +13,113 @@ import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
|||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
|
||||
class JobsContent extends StatelessWidget {
|
||||
const JobsContent({super.key});
|
||||
const JobsContent({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => BlocBuilder<JobsCubit, JobsState>(
|
||||
builder: (final context, final state) {
|
||||
late List<Widget> widgets;
|
||||
final ServerInstallationState installationState = context.read<ServerInstallationCubit>().state;
|
||||
if (state is JobsStateEmpty) {
|
||||
widgets = [
|
||||
const SizedBox(height: 80),
|
||||
Center(child: BrandText.body1('jobs.empty'.tr())),
|
||||
];
|
||||
|
||||
if (installationState is ServerInstallationFinished) {
|
||||
builder: (final context, final state) {
|
||||
late List<Widget> widgets;
|
||||
final ServerInstallationState installationState =
|
||||
context.read<ServerInstallationCubit>().state;
|
||||
if (state is JobsStateEmpty) {
|
||||
widgets = [
|
||||
...widgets,
|
||||
const SizedBox(height: 80),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().upgradeServer(),
|
||||
text: 'jobs.upgradeServer'.tr(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BrandButton.text(
|
||||
onPressed: () {
|
||||
final NavigationService nav = getIt<NavigationService>();
|
||||
nav.showPopUpDialog(BrandAlert(
|
||||
title: 'jobs.rebootServer'.tr(),
|
||||
contentText: 'modals.3'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
Center(child: BrandText.body1('jobs.empty'.tr())),
|
||||
];
|
||||
|
||||
if (installationState is ServerInstallationFinished) {
|
||||
widgets = [
|
||||
...widgets,
|
||||
const SizedBox(height: 80),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().upgradeServer(),
|
||||
text: 'jobs.upgradeServer'.tr(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BrandButton.text(
|
||||
onPressed: () {
|
||||
final NavigationService nav = getIt<NavigationService>();
|
||||
nav.showPopUpDialog(
|
||||
BrandAlert(
|
||||
title: 'jobs.rebootServer'.tr(),
|
||||
contentText: 'modals.3'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
),
|
||||
ActionButton(
|
||||
onPressed: () =>
|
||||
{context.read<JobsCubit>().rebootServer()},
|
||||
text: 'modals.9'.tr(),
|
||||
)
|
||||
],
|
||||
),
|
||||
ActionButton(
|
||||
onPressed: () =>
|
||||
{context.read<JobsCubit>().rebootServer()},
|
||||
text: 'modals.9'.tr(),
|
||||
)
|
||||
],
|
||||
),);
|
||||
},
|
||||
title: 'jobs.rebootServer'.tr(),
|
||||
);
|
||||
},
|
||||
title: 'jobs.rebootServer'.tr(),
|
||||
),
|
||||
];
|
||||
}
|
||||
} else if (state is JobsStateLoading) {
|
||||
widgets = [
|
||||
const SizedBox(height: 80),
|
||||
BrandLoader.horizontal(),
|
||||
];
|
||||
} else if (state is JobsStateWithJobs) {
|
||||
widgets = [
|
||||
...state.jobList
|
||||
.map(
|
||||
(final j) => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BrandCards.small(
|
||||
child: Text(j.title),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary:
|
||||
Theme.of(context).colorScheme.errorContainer,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
onPressed: () =>
|
||||
context.read<JobsCubit>().removeJob(j.id),
|
||||
child: Text(
|
||||
'basis.remove'.tr(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onErrorContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
const SizedBox(height: 20),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().applyAll(),
|
||||
text: 'jobs.start'.tr(),
|
||||
),
|
||||
];
|
||||
}
|
||||
} else if (state is JobsStateLoading) {
|
||||
widgets = [
|
||||
const SizedBox(height: 80),
|
||||
BrandLoader.horizontal(),
|
||||
];
|
||||
} else if (state is JobsStateWithJobs) {
|
||||
widgets = [
|
||||
...state.jobList
|
||||
.map(
|
||||
(final j) => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BrandCards.small(
|
||||
child: Text(j.title),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).colorScheme.errorContainer,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
onPressed: () =>
|
||||
context.read<JobsCubit>().removeJob(j.id),
|
||||
child: Text('basis.remove'.tr(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onErrorContainer,),),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
const SizedBox(height: 20),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().applyAll(),
|
||||
text: 'jobs.start'.tr(),
|
||||
),
|
||||
];
|
||||
}
|
||||
return ListView(
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
const SizedBox(height: 15),
|
||||
Center(
|
||||
child: BrandText.h2(
|
||||
'jobs.title'.tr(),
|
||||
return ListView(
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
const SizedBox(height: 15),
|
||||
Center(
|
||||
child: BrandText.h2(
|
||||
'jobs.title'.tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
...widgets
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
const SizedBox(height: 20),
|
||||
...widgets
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,47 +6,49 @@ import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class NotReadyCard extends StatelessWidget {
|
||||
const NotReadyCard({super.key});
|
||||
const NotReadyCard({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15), color: BrandColors.gray6,),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'not_ready_card.1'.tr(),
|
||||
style: const TextStyle(color: BrandColors.white),
|
||||
),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0.5),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const InitializingPage(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: BrandColors.gray6,
|
||||
),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'not_ready_card.1'.tr(),
|
||||
style: const TextStyle(color: BrandColors.white),
|
||||
),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0.5),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const InitializingPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'not_ready_card.2'.tr(),
|
||||
style: body1Style.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.underline,
|
||||
// height: 1.1,
|
||||
child: Text(
|
||||
'not_ready_card.2'.tr(),
|
||||
style: body1Style.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.underline,
|
||||
// height: 1.1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'not_ready_card.3'.tr(),
|
||||
style: const TextStyle(color: BrandColors.white),
|
||||
),
|
||||
],
|
||||
TextSpan(
|
||||
text: 'not_ready_card.3'.tr(),
|
||||
style: const TextStyle(color: BrandColors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import 'package:selfprivacy/ui/components/pre_styled_buttons/pre_styled_buttons.
|
|||
|
||||
class OnePage extends StatelessWidget {
|
||||
const OnePage({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.child,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final String title;
|
||||
|
@ -16,32 +16,33 @@ class OnePage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 51,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: BrandText.h4('basis.details'.tr()),
|
||||
),
|
||||
const BrandDivider(),
|
||||
],
|
||||
),
|
||||
),
|
||||
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(),),
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 51,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: BrandText.h4('basis.details'.tr()),
|
||||
),
|
||||
const BrandDivider(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
part of 'pre_styled_buttons.dart';
|
||||
|
||||
class _CloseButton extends StatelessWidget {
|
||||
const _CloseButton({super.key, required this.onPress});
|
||||
const _CloseButton({required this.onPress});
|
||||
|
||||
final VoidCallback onPress;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => OutlinedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BrandText.h4('basis.close'.tr()),
|
||||
const Icon(Icons.close),
|
||||
],
|
||||
),
|
||||
);
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BrandText.h4('basis.close'.tr()),
|
||||
const Icon(Icons.close),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
part of 'pre_styled_buttons.dart';
|
||||
|
||||
class _BrandFlashButton extends StatefulWidget {
|
||||
const _BrandFlashButton({super.key});
|
||||
|
||||
@override
|
||||
_BrandFlashButtonState createState() => _BrandFlashButtonState();
|
||||
}
|
||||
|
@ -15,7 +13,9 @@ class _BrandFlashButtonState extends State<_BrandFlashButton>
|
|||
@override
|
||||
void initState() {
|
||||
_animationController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 800),);
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
);
|
||||
_colorTween = ColorTween(
|
||||
begin: BrandColors.black,
|
||||
end: BrandColors.primary,
|
||||
|
@ -45,32 +45,34 @@ class _BrandFlashButtonState extends State<_BrandFlashButton>
|
|||
bool wasPrevStateIsEmpty = true;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => BlocListener<JobsCubit, JobsState>(
|
||||
listener: (final context, final state) {
|
||||
if (wasPrevStateIsEmpty && state is! JobsStateEmpty) {
|
||||
wasPrevStateIsEmpty = false;
|
||||
_animationController.forward();
|
||||
} else if (!wasPrevStateIsEmpty && state is JobsStateEmpty) {
|
||||
wasPrevStateIsEmpty = true;
|
||||
Widget build(final BuildContext context) =>
|
||||
BlocListener<JobsCubit, JobsState>(
|
||||
listener: (final context, final state) {
|
||||
if (wasPrevStateIsEmpty && state is! JobsStateEmpty) {
|
||||
wasPrevStateIsEmpty = false;
|
||||
_animationController.forward();
|
||||
} else if (!wasPrevStateIsEmpty && state is JobsStateEmpty) {
|
||||
wasPrevStateIsEmpty = true;
|
||||
|
||||
_animationController.reverse();
|
||||
}
|
||||
},
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
showBrandBottomSheet(
|
||||
context: context,
|
||||
builder: (final context) => const BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: JobsContent(),
|
||||
),
|
||||
);
|
||||
_animationController.reverse();
|
||||
}
|
||||
},
|
||||
icon: AnimatedBuilder(
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
showBrandBottomSheet(
|
||||
context: context,
|
||||
builder: (final context) => const BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: JobsContent(),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: AnimatedBuilder(
|
||||
animation: _colorTween,
|
||||
builder: (final context, final child) {
|
||||
final double v = _animationController.value;
|
||||
final IconData icon = v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
|
||||
final IconData icon =
|
||||
v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
|
||||
return Transform.scale(
|
||||
scale: 1 + (v < 0.5 ? v : 1 - v) * 2,
|
||||
child: Icon(
|
||||
|
@ -78,7 +80,8 @@ class _BrandFlashButtonState extends State<_BrandFlashButton>
|
|||
color: _colorTween.value,
|
||||
),
|
||||
);
|
||||
},),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
|
|||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||
|
||||
class BrandFab extends StatefulWidget {
|
||||
const BrandFab({super.key});
|
||||
const BrandFab({final super.key});
|
||||
|
||||
@override
|
||||
State<BrandFab> createState() => _BrandFabState();
|
||||
|
@ -21,7 +21,9 @@ class _BrandFabState extends State<BrandFab>
|
|||
@override
|
||||
void initState() {
|
||||
_animationController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 800),);
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -62,18 +64,20 @@ class _BrandFabState extends State<BrandFab>
|
|||
);
|
||||
},
|
||||
child: AnimatedBuilder(
|
||||
animation: _colorTween,
|
||||
builder: (final BuildContext context, final Widget? child) {
|
||||
final double v = _animationController.value;
|
||||
final IconData icon = v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
|
||||
return Transform.scale(
|
||||
scale: 1 + (v < 0.5 ? v : 1 - v) * 2,
|
||||
child: Icon(
|
||||
icon,
|
||||
color: _colorTween.value,
|
||||
),
|
||||
);
|
||||
},),
|
||||
animation: _colorTween,
|
||||
builder: (final BuildContext context, final Widget? child) {
|
||||
final double v = _animationController.value;
|
||||
final IconData icon =
|
||||
v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
|
||||
return Transform.scale(
|
||||
scale: 1 + (v < 0.5 ? v : 1 - v) * 2,
|
||||
child: Icon(
|
||||
icon,
|
||||
color: _colorTween.value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,5 +18,5 @@ class PreStyledButtons {
|
|||
}) =>
|
||||
_CloseButton(onPress: onPress);
|
||||
|
||||
static Widget flash() => const _BrandFlashButton();
|
||||
static Widget flash() => _BrandFlashButton();
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|||
|
||||
class ProgressBar extends StatefulWidget {
|
||||
const ProgressBar({
|
||||
super.key,
|
||||
required this.steps,
|
||||
required this.activeIndex,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final int activeIndex;
|
||||
|
@ -23,9 +23,11 @@ class ProgressBar extends StatefulWidget {
|
|||
class _ProgressBarState extends State<ProgressBar> {
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final double progress = 1 / widget.steps.length * (widget.activeIndex + 0.3);
|
||||
final double progress =
|
||||
1 / widget.steps.length * (widget.activeIndex + 0.3);
|
||||
final bool isDark = context.watch<AppSettingsCubit>().state.isDarkModeOn;
|
||||
final TextStyle style = isDark ? progressTextStyleDark : progressTextStyleLight;
|
||||
final TextStyle style =
|
||||
isDark ? progressTextStyleDark : progressTextStyleLight;
|
||||
|
||||
final Iterable<Container> allSteps = widget.steps.asMap().map(
|
||||
(final i, final step) {
|
||||
|
@ -77,20 +79,20 @@ class _ProgressBarState extends State<ProgressBar> {
|
|||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (final _, final constraints) => AnimatedContainer(
|
||||
width: constraints.maxWidth * progress,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: BrandColors.stableGradientColors,
|
||||
),
|
||||
),
|
||||
duration: const Duration(
|
||||
milliseconds: 300,
|
||||
width: constraints.maxWidth * progress,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: BrandColors.stableGradientColors,
|
||||
),
|
||||
),
|
||||
duration: const Duration(
|
||||
milliseconds: 300,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
|
@ -120,11 +122,15 @@ class _ProgressBarState extends State<ProgressBar> {
|
|||
text: TextSpan(
|
||||
style: progressTextStyleLight,
|
||||
children: [
|
||||
if (checked) const WidgetSpan(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 2, right: 2),
|
||||
child: Icon(BrandIcons.check, size: 11),
|
||||
),) else TextSpan(text: '${index + 1}.', style: style),
|
||||
if (checked)
|
||||
const WidgetSpan(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 2, right: 2),
|
||||
child: Icon(BrandIcons.check, size: 11),
|
||||
),
|
||||
)
|
||||
else
|
||||
TextSpan(text: '${index + 1}.', style: style),
|
||||
TextSpan(text: step, style: style)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -3,10 +3,10 @@ import 'package:selfprivacy/config/brand_colors.dart';
|
|||
|
||||
class SwitcherBlock extends StatelessWidget {
|
||||
const SwitcherBlock({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.isActive,
|
||||
required this.onChange,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
@ -15,24 +15,25 @@ class SwitcherBlock extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Container(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
decoration: const BoxDecoration(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||
),),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(child: child),
|
||||
const SizedBox(width: 5),
|
||||
Switch(
|
||||
activeColor: BrandColors.green1,
|
||||
activeTrackColor: BrandColors.green2,
|
||||
onChanged: onChange,
|
||||
value: isActive,
|
||||
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(child: child),
|
||||
const SizedBox(width: 5),
|
||||
Switch(
|
||||
activeColor: BrandColors.green1,
|
||||
activeTrackColor: BrandColors.green2,
|
||||
onChanged: onChange,
|
||||
value: isActive,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
|||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class BackupDetails extends StatefulWidget {
|
||||
const BackupDetails({super.key});
|
||||
const BackupDetails({final super.key});
|
||||
|
||||
@override
|
||||
State<BackupDetails> createState() => _BackupDetailsState();
|
||||
|
@ -30,14 +30,17 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
Widget build(final BuildContext context) {
|
||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
final bool isBackupInitialized = context.watch<BackupsCubit>().state.isInitialized;
|
||||
final BackupStatusEnum backupStatus = context.watch<BackupsCubit>().state.status;
|
||||
final bool isBackupInitialized =
|
||||
context.watch<BackupsCubit>().state.isInitialized;
|
||||
final BackupStatusEnum backupStatus =
|
||||
context.watch<BackupsCubit>().state.status;
|
||||
final StateType providerState = isReady && isBackupInitialized
|
||||
? (backupStatus == BackupStatusEnum.error
|
||||
? StateType.warning
|
||||
: StateType.stable)
|
||||
: StateType.uninitialized;
|
||||
final bool preventActions = context.watch<BackupsCubit>().state.preventActions;
|
||||
final bool preventActions =
|
||||
context.watch<BackupsCubit>().state.preventActions;
|
||||
final double backupProgress = context.watch<BackupsCubit>().state.progress;
|
||||
final String backupError = context.watch<BackupsCubit>().state.error;
|
||||
final List<Backup> backups = context.watch<BackupsCubit>().state.backups;
|
||||
|
@ -84,7 +87,8 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
ListTile(
|
||||
title: Text(
|
||||
'providers.backup.creating'.tr(
|
||||
args: [(backupProgress * 100).round().toString()],),
|
||||
args: [(backupProgress * 100).round().toString()],
|
||||
),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
subtitle: LinearProgressIndicator(
|
||||
|
@ -96,7 +100,8 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
ListTile(
|
||||
title: Text(
|
||||
'providers.backup.restoring'.tr(
|
||||
args: [(backupProgress * 100).round().toString()],),
|
||||
args: [(backupProgress * 100).round().toString()],
|
||||
),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
subtitle: LinearProgressIndicator(
|
||||
|
@ -148,34 +153,44 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
),
|
||||
if (backups.isNotEmpty)
|
||||
Column(
|
||||
children: backups.map((final Backup backup) => ListTile(
|
||||
onTap: preventActions
|
||||
? null
|
||||
: () {
|
||||
final NavigationService 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(),
|
||||
children: backups
|
||||
.map(
|
||||
(final Backup backup) => ListTile(
|
||||
onTap: preventActions
|
||||
? null
|
||||
: () {
|
||||
final NavigationService 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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'package:selfprivacy/ui/pages/devices/new_device.dart';
|
|||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
class DevicesScreen extends StatefulWidget {
|
||||
const DevicesScreen({super.key});
|
||||
const DevicesScreen({final super.key});
|
||||
|
||||
@override
|
||||
State<DevicesScreen> createState() => _DevicesScreenState();
|
||||
|
@ -18,7 +18,8 @@ class DevicesScreen extends StatefulWidget {
|
|||
class _DevicesScreenState extends State<DevicesScreen> {
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final ApiDevicesState devicesStatus = context.watch<ApiDevicesCubit>().state;
|
||||
final ApiDevicesState devicesStatus =
|
||||
context.watch<ApiDevicesCubit>().state;
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
|
@ -79,61 +80,68 @@ class _DevicesScreenState extends State<DevicesScreen> {
|
|||
}
|
||||
|
||||
class _DeviceTile extends StatelessWidget {
|
||||
const _DeviceTile({super.key, required this.device});
|
||||
const _DeviceTile({required this.device});
|
||||
|
||||
final ApiToken device;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
|
||||
title: Text(device.name),
|
||||
subtitle: Text('devices.main_screen.access_granted_on'
|
||||
.tr(args: [DateFormat.yMMMMd().format(device.date)]),),
|
||||
onTap: device.isCaller
|
||||
? null
|
||||
: () => _showConfirmationDialog(context, device),
|
||||
);
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
|
||||
title: Text(device.name),
|
||||
subtitle: Text(
|
||||
'devices.main_screen.access_granted_on'
|
||||
.tr(args: [DateFormat.yMMMMd().format(device.date)]),
|
||||
),
|
||||
onTap: device.isCaller
|
||||
? null
|
||||
: () => _showConfirmationDialog(context, device),
|
||||
);
|
||||
|
||||
Future _showConfirmationDialog(final BuildContext context, final ApiToken device) => showDialog(
|
||||
Future _showConfirmationDialog(
|
||||
final BuildContext context,
|
||||
final ApiToken device,
|
||||
) =>
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (final context) => AlertDialog(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.link_off_outlined),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'devices.revoke_device_alert.header'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'devices.revoke_device_alert.description'
|
||||
.tr(args: [device.name]),
|
||||
style: Theme.of(context).textTheme.bodyMedium,),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('devices.revoke_device_alert.no'.tr()),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('devices.revoke_device_alert.yes'.tr()),
|
||||
onPressed: () {
|
||||
context.read<ApiDevicesCubit>().deleteDevice(device);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.link_off_outlined),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'devices.revoke_device_alert.header'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'devices.revoke_device_alert.description'
|
||||
.tr(args: [device.name]),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('devices.revoke_device_alert.no'.tr()),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('devices.revoke_device_alert.yes'.tr()),
|
||||
onPressed: () {
|
||||
context.read<ApiDevicesCubit>().deleteDevice(device);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,74 +7,77 @@ import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
|
|||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
|
||||
class NewDeviceScreen extends StatelessWidget {
|
||||
const NewDeviceScreen({super.key});
|
||||
const NewDeviceScreen({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => BrandHeroScreen(
|
||||
heroTitle: 'devices.add_new_device_screen.header'.tr(),
|
||||
heroSubtitle: 'devices.add_new_device_screen.description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: context.read<ApiDevicesCubit>().getNewDeviceKey(),
|
||||
builder: (final BuildContext context, final AsyncSnapshot<Object?> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return _KeyDisplay(
|
||||
newDeviceKey: snapshot.data.toString(),
|
||||
);
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
heroTitle: 'devices.add_new_device_screen.header'.tr(),
|
||||
heroSubtitle: 'devices.add_new_device_screen.description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: context.read<ApiDevicesCubit>().getNewDeviceKey(),
|
||||
builder: (
|
||||
final BuildContext context,
|
||||
final AsyncSnapshot<Object?> snapshot,
|
||||
) {
|
||||
if (snapshot.hasData) {
|
||||
return _KeyDisplay(
|
||||
newDeviceKey: snapshot.data.toString(),
|
||||
);
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class _KeyDisplay extends StatelessWidget {
|
||||
const _KeyDisplay({super.key, required this.newDeviceKey});
|
||||
const _KeyDisplay({required this.newDeviceKey});
|
||||
final String newDeviceKey;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
newDeviceKey,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 24,
|
||||
fontFamily: 'RobotoMono',
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'devices.add_new_device_screen.tip'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium!,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
child: Text(
|
||||
'basis.done'.tr(),
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
newDeviceKey,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 24,
|
||||
fontFamily: 'RobotoMono',
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
);
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'devices.add_new_device_screen.tip'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium!,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
child: Text(
|
||||
'basis.done'.tr(),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,14 +8,17 @@ import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.da
|
|||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
|
||||
class DnsDetailsPage extends StatefulWidget {
|
||||
const DnsDetailsPage({super.key});
|
||||
const DnsDetailsPage({final super.key});
|
||||
|
||||
@override
|
||||
State<DnsDetailsPage> createState() => _DnsDetailsPageState();
|
||||
}
|
||||
|
||||
class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
||||
Widget _getStateCard(final DnsRecordsStatus dnsState, final Function fixCallback) {
|
||||
Widget _getStateCard(
|
||||
final DnsRecordsStatus dnsState,
|
||||
final Function fixCallback,
|
||||
) {
|
||||
String description = '';
|
||||
String subtitle = '';
|
||||
Icon icon = const Icon(
|
||||
|
@ -66,7 +69,8 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
|||
Widget build(final BuildContext context) {
|
||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
final String domain = getIt<ApiConfigModel>().serverDomain?.domainName ?? '';
|
||||
final String domain =
|
||||
getIt<ApiConfigModel>().serverDomain?.domainName ?? '';
|
||||
final DnsRecordsState dnsCubit = context.watch<DnsRecordsCubit>().state;
|
||||
|
||||
print(dnsCubit.dnsState);
|
||||
|
|
|
@ -4,19 +4,21 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
const AboutPage({super.key});
|
||||
const AboutPage({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'more.about_project'.tr(), hasBackButton: true,),
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'more.about_project'.tr(),
|
||||
hasBackButton: true,
|
||||
),
|
||||
),
|
||||
body: const BrandMarkdown(
|
||||
fileName: 'about',
|
||||
),
|
||||
),
|
||||
body: const BrandMarkdown(
|
||||
fileName: 'about',
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:selfprivacy/utils/named_font_weight.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class AppSettingsPage extends StatefulWidget {
|
||||
const AppSettingsPage({super.key});
|
||||
const AppSettingsPage({final super.key});
|
||||
|
||||
@override
|
||||
State<AppSettingsPage> createState() => _AppSettingsPageState();
|
||||
|
@ -22,14 +22,18 @@ class AppSettingsPage extends StatefulWidget {
|
|||
class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final bool isDarkModeOn = context.watch<AppSettingsCubit>().state.isDarkModeOn;
|
||||
final bool isDarkModeOn =
|
||||
context.watch<AppSettingsCubit>().state.isDarkModeOn;
|
||||
|
||||
return SafeArea(
|
||||
child: Builder(builder: (final context) => Scaffold(
|
||||
child: Builder(
|
||||
builder: (final context) => Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'more.settings.title'.tr(), hasBackButton: true,),
|
||||
title: 'more.settings.title'.tr(),
|
||||
hasBackButton: true,
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
padding: paddingH15V0,
|
||||
|
@ -38,9 +42,11 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
|||
Container(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||
),),
|
||||
border: Border(
|
||||
bottom:
|
||||
BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
@ -65,9 +71,11 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
|||
Container(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||
),),
|
||||
border: Border(
|
||||
bottom:
|
||||
BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
@ -95,23 +103,24 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
|||
showDialog(
|
||||
context: context,
|
||||
builder: (final _) => BrandAlert(
|
||||
title: 'modals.3'.tr(),
|
||||
contentText: 'modals.4'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'modals.5'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () {
|
||||
context
|
||||
.read<ServerInstallationCubit>()
|
||||
.clearAppConfig();
|
||||
Navigator.of(context).pop();
|
||||
},),
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: 'modals.3'.tr(),
|
||||
contentText: 'modals.4'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'modals.5'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () {
|
||||
context
|
||||
.read<ServerInstallationCubit>()
|
||||
.clearAppConfig();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -121,7 +130,8 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
|||
deleteServer(context)
|
||||
],
|
||||
),
|
||||
),),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -131,9 +141,10 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
|||
return Container(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||
),),
|
||||
border: Border(
|
||||
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
@ -156,31 +167,34 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
|||
showDialog(
|
||||
context: context,
|
||||
builder: (final _) => BrandAlert(
|
||||
title: 'modals.3'.tr(),
|
||||
contentText: 'modals.6'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'modals.7'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () async {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (final context) => Container(
|
||||
alignment: Alignment.center,
|
||||
child:
|
||||
const CircularProgressIndicator(),
|
||||
),);
|
||||
await context
|
||||
.read<ServerInstallationCubit>()
|
||||
.serverDelete();
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
},),
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: 'modals.3'.tr(),
|
||||
contentText: 'modals.6'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'modals.7'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () async {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (final context) => Container(
|
||||
alignment: Alignment.center,
|
||||
child: const CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
await context
|
||||
.read<ServerInstallationCubit>()
|
||||
.serverDelete();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
|
@ -199,7 +213,6 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
|||
|
||||
class _TextColumn extends StatelessWidget {
|
||||
const _TextColumn({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
this.hasWarning = false,
|
||||
|
@ -210,19 +223,21 @@ class _TextColumn extends StatelessWidget {
|
|||
final bool hasWarning;
|
||||
@override
|
||||
Widget build(final BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BrandText.body1(
|
||||
title,
|
||||
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
BrandText.body1(value,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BrandText.body1(
|
||||
title,
|
||||
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
BrandText.body1(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
height: 1.53,
|
||||
color: BrandColors.gray1,
|
||||
).merge(TextStyle(color: hasWarning ? BrandColors.warning : null)),),
|
||||
],
|
||||
);
|
||||
).merge(TextStyle(color: hasWarning ? BrandColors.warning : null)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
|||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||
|
||||
class Console extends StatefulWidget {
|
||||
const Console({super.key});
|
||||
const Console({final super.key});
|
||||
|
||||
@override
|
||||
State<Console> createState() => _ConsoleState();
|
||||
|
@ -32,68 +32,77 @@ class _ConsoleState extends State<Console> {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(53),
|
||||
child: Column(
|
||||
children: const [
|
||||
BrandHeader(title: 'Console', hasBackButton: true),
|
||||
BrandDivider(),
|
||||
],
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(53),
|
||||
child: Column(
|
||||
children: const [
|
||||
BrandHeader(title: 'Console', hasBackButton: true),
|
||||
BrandDivider(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: getIt.allReady(),
|
||||
builder: (final BuildContext context, final AsyncSnapshot<void> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final List<Message> messages = getIt.get<ConsoleModel>().messages;
|
||||
body: FutureBuilder(
|
||||
future: getIt.allReady(),
|
||||
builder: (
|
||||
final BuildContext context,
|
||||
final AsyncSnapshot<void> snapshot,
|
||||
) {
|
||||
if (snapshot.hasData) {
|
||||
final List<Message> messages =
|
||||
getIt.get<ConsoleModel>().messages;
|
||||
|
||||
return ListView(
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
...UnmodifiableListView(messages
|
||||
.map((final message) {
|
||||
final bool isError = message.type == MessageType.warning;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: DefaultTextStyle.of(context).style,
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text:
|
||||
'${message.timeString}${isError ? '(Error)' : ''}: \n',
|
||||
style: TextStyle(
|
||||
return ListView(
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
...UnmodifiableListView(
|
||||
messages
|
||||
.map((final message) {
|
||||
final bool isError =
|
||||
message.type == MessageType.warning;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: DefaultTextStyle.of(context).style,
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text:
|
||||
'${message.timeString}${isError ? '(Error)' : ''}: \n',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
isError ? BrandColors.red1 : null,),),
|
||||
TextSpan(text: message.text),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
.toList()
|
||||
.reversed,),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Text('Waiting for initialisation'),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
CircularProgressIndicator(),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
isError ? BrandColors.red1 : null,
|
||||
),
|
||||
),
|
||||
TextSpan(text: message.text),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
.toList()
|
||||
.reversed,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Text('Waiting for initialisation'),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
CircularProgressIndicator(),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,28 +7,32 @@ import 'package:package_info/package_info.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class InfoPage extends StatelessWidget {
|
||||
const InfoPage({super.key});
|
||||
const InfoPage({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(title: 'more.about_app'.tr(), hasBackButton: true),
|
||||
),
|
||||
body: ListView(
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
const BrandDivider(),
|
||||
const SizedBox(height: 10),
|
||||
FutureBuilder(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child:
|
||||
BrandHeader(title: 'more.about_app'.tr(), hasBackButton: true),
|
||||
),
|
||||
body: ListView(
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
const BrandDivider(),
|
||||
const SizedBox(height: 10),
|
||||
FutureBuilder(
|
||||
future: _version(),
|
||||
builder: (final context, final snapshot) => BrandText.body1('more.about_app_page.text'
|
||||
.tr(args: [snapshot.data.toString()]),),),
|
||||
],
|
||||
builder: (final context, final snapshot) => BrandText.body1(
|
||||
'more.about_app_page.text'
|
||||
.tr(args: [snapshot.data.toString()]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
|
||||
Future<String> _version() async {
|
||||
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
|
|
@ -21,7 +21,7 @@ import 'package:selfprivacy/ui/pages/more/console/console.dart';
|
|||
import 'package:selfprivacy/ui/pages/more/info/info.dart';
|
||||
|
||||
class MorePage extends StatelessWidget {
|
||||
const MorePage({super.key});
|
||||
const MorePage({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
|
@ -51,11 +51,12 @@ class MorePage extends StatelessWidget {
|
|||
),
|
||||
if (isReady)
|
||||
_MoreMenuItem(
|
||||
title: 'more.create_ssh_key'.tr(),
|
||||
iconData: Ionicons.key_outline,
|
||||
goTo: SshKeysPage(
|
||||
user: context.read<UsersCubit>().state.rootUser,
|
||||
),),
|
||||
title: 'more.create_ssh_key'.tr(),
|
||||
iconData: Ionicons.key_outline,
|
||||
goTo: SshKeysPage(
|
||||
user: context.read<UsersCubit>().state.rootUser,
|
||||
),
|
||||
),
|
||||
if (isReady)
|
||||
_MoreMenuItem(
|
||||
iconData: Icons.password_outlined,
|
||||
|
@ -105,7 +106,6 @@ class MorePage extends StatelessWidget {
|
|||
|
||||
class _MoreMenuItem extends StatelessWidget {
|
||||
const _MoreMenuItem({
|
||||
super.key,
|
||||
required this.iconData,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
|
|
|
@ -6,7 +6,7 @@ import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class OnboardingPage extends StatefulWidget {
|
||||
const OnboardingPage({super.key, required this.nextPage});
|
||||
const OnboardingPage({required this.nextPage, final super.key});
|
||||
|
||||
final Widget nextPage;
|
||||
@override
|
||||
|
@ -23,111 +23,111 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
body: PageView(
|
||||
controller: pageController,
|
||||
children: [
|
||||
_withPadding(firstPage()),
|
||||
_withPadding(secondPage()),
|
||||
],
|
||||
child: Scaffold(
|
||||
body: PageView(
|
||||
controller: pageController,
|
||||
children: [
|
||||
_withPadding(firstPage()),
|
||||
_withPadding(secondPage()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
|
||||
Widget _withPadding(final Widget child) => Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
|
||||
Widget firstPage() => ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
BrandText.h2(
|
||||
'onboarding.page1_title'.tr(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
BrandText.body2('onboarding.page1_text'.tr()),
|
||||
Flexible(
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
_fileName(
|
||||
context: context,
|
||||
path: 'assets/images/onboarding',
|
||||
fileExtention: 'png',
|
||||
fileName: 'onboarding1',
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
BrandText.h2(
|
||||
'onboarding.page1_title'.tr(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
BrandText.body2('onboarding.page1_text'.tr()),
|
||||
Flexible(
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
_fileName(
|
||||
context: context,
|
||||
path: 'assets/images/onboarding',
|
||||
fileExtention: 'png',
|
||||
fileName: 'onboarding1',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
pageController.animateToPage(
|
||||
1,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
},
|
||||
text: 'basis.next'.tr(),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
);
|
||||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
pageController.animateToPage(
|
||||
1,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
},
|
||||
text: 'basis.next'.tr(),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget secondPage() => ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
BrandText.h2('onboarding.page2_title'.tr()),
|
||||
const SizedBox(height: 20),
|
||||
BrandText.body2('onboarding.page2_text'.tr()),
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: Image.asset(
|
||||
_fileName(
|
||||
context: context,
|
||||
path: 'assets/images/onboarding',
|
||||
fileExtention: 'png',
|
||||
fileName: 'logos_line',
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Center(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
BrandText.h2('onboarding.page2_title'.tr()),
|
||||
const SizedBox(height: 20),
|
||||
BrandText.body2('onboarding.page2_text'.tr()),
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: Image.asset(
|
||||
_fileName(
|
||||
context: context,
|
||||
path: 'assets/images/onboarding',
|
||||
fileExtention: 'png',
|
||||
fileName: 'onboarding2',
|
||||
fileName: 'logos_line',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
context.read<AppSettingsCubit>().turnOffOnboarding();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(widget.nextPage),
|
||||
(final route) => false,
|
||||
);
|
||||
},
|
||||
text: 'basis.got_it'.tr(),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
);
|
||||
Flexible(
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
_fileName(
|
||||
context: context,
|
||||
path: 'assets/images/onboarding',
|
||||
fileExtention: 'png',
|
||||
fileName: 'onboarding2',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
context.read<AppSettingsCubit>().turnOffOnboarding();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(widget.nextPage),
|
||||
(final route) => false,
|
||||
);
|
||||
},
|
||||
text: 'basis.got_it'.tr(),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _fileName({
|
||||
|
|
|
@ -21,7 +21,7 @@ import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class ProvidersPage extends StatefulWidget {
|
||||
const ProvidersPage({super.key});
|
||||
const ProvidersPage({final super.key});
|
||||
|
||||
@override
|
||||
State<ProvidersPage> createState() => _ProvidersPageState();
|
||||
|
@ -32,8 +32,10 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
Widget build(final BuildContext context) {
|
||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
final bool isBackupInitialized = context.watch<BackupsCubit>().state.isInitialized;
|
||||
final DnsRecordsStatus dnsStatus = context.watch<DnsRecordsCubit>().state.dnsState;
|
||||
final bool isBackupInitialized =
|
||||
context.watch<BackupsCubit>().state.isInitialized;
|
||||
final DnsRecordsStatus dnsStatus =
|
||||
context.watch<DnsRecordsCubit>().state.dnsState;
|
||||
|
||||
StateType getDnsStatus() {
|
||||
if (dnsStatus == DnsRecordsStatus.uninitialized ||
|
||||
|
@ -87,7 +89,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
}
|
||||
|
||||
class _Card extends StatelessWidget {
|
||||
const _Card({super.key, required this.provider});
|
||||
const _Card({required this.provider});
|
||||
|
||||
final ProviderModel provider;
|
||||
@override
|
||||
|
@ -122,17 +124,21 @@ class _Card extends StatelessWidget {
|
|||
message = domainName;
|
||||
stableText = 'providers.domain.status'.tr();
|
||||
|
||||
onTap = () => Navigator.of(context).push(materialRoute(
|
||||
const DnsDetailsPage(),
|
||||
),);
|
||||
onTap = () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const DnsDetailsPage(),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ProviderType.backup:
|
||||
title = 'providers.backup.card_title'.tr();
|
||||
stableText = 'providers.backup.status'.tr();
|
||||
|
||||
onTap = () => Navigator.of(context).push(materialRoute(
|
||||
const BackupDetails(),
|
||||
),);
|
||||
onTap = () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const BackupDetails(),
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
return GestureDetector(
|
||||
|
|
|
@ -14,7 +14,7 @@ import 'package:selfprivacy/ui/pages/recovery_key/recovery_key_receiving.dart';
|
|||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
class RecoveryKey extends StatefulWidget {
|
||||
const RecoveryKey({super.key});
|
||||
const RecoveryKey({final super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryKey> createState() => _RecoveryKeyState();
|
||||
|
@ -61,7 +61,7 @@ class _RecoveryKeyState extends State<RecoveryKey> {
|
|||
}
|
||||
|
||||
class RecoveryKeyContent extends StatefulWidget {
|
||||
const RecoveryKeyContent({super.key});
|
||||
const RecoveryKeyContent({final super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryKeyContent> createState() => _RecoveryKeyContentState();
|
||||
|
@ -107,50 +107,51 @@ class _RecoveryKeyContentState extends State<RecoveryKeyContent> {
|
|||
}
|
||||
|
||||
class RecoveryKeyStatusCard extends StatelessWidget {
|
||||
const RecoveryKeyStatusCard({required this.isValid, super.key});
|
||||
const RecoveryKeyStatusCard({required this.isValid, final super.key});
|
||||
|
||||
final bool isValid;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => BrandCards.filled(
|
||||
child: ListTile(
|
||||
title: isValid
|
||||
? Text(
|
||||
'recovery_key.key_valid'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'recovery_key.key_invalid'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
leading: isValid
|
||||
? Icon(
|
||||
Icons.check_circle_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
)
|
||||
: Icon(
|
||||
Icons.cancel_outlined,
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
tileColor: isValid
|
||||
? Theme.of(context).colorScheme.surfaceVariant
|
||||
: Theme.of(context).colorScheme.errorContainer,
|
||||
),
|
||||
);
|
||||
child: ListTile(
|
||||
title: isValid
|
||||
? Text(
|
||||
'recovery_key.key_valid'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'recovery_key.key_invalid'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
leading: isValid
|
||||
? Icon(
|
||||
Icons.check_circle_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
)
|
||||
: Icon(
|
||||
Icons.cancel_outlined,
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
tileColor: isValid
|
||||
? Theme.of(context).colorScheme.surfaceVariant
|
||||
: Theme.of(context).colorScheme.errorContainer,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class RecoveryKeyInformation extends StatelessWidget {
|
||||
const RecoveryKeyInformation({required this.state, super.key});
|
||||
const RecoveryKeyInformation({required this.state, final super.key});
|
||||
|
||||
final RecoveryKeyState state;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
const EdgeInsets padding = EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0);
|
||||
const EdgeInsets padding =
|
||||
EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0);
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
|
@ -191,7 +192,7 @@ class RecoveryKeyInformation extends StatelessWidget {
|
|||
}
|
||||
|
||||
class RecoveryKeyConfiguration extends StatefulWidget {
|
||||
const RecoveryKeyConfiguration({super.key});
|
||||
const RecoveryKeyConfiguration({final super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _RecoveryKeyConfigurationState();
|
||||
|
@ -217,11 +218,13 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
final String token = await context.read<RecoveryKeyCubit>().generateRecoveryKey(
|
||||
numberOfUses:
|
||||
_isAmountToggled ? int.tryParse(_amountController.text) : null,
|
||||
expirationDate: _isExpirationToggled ? _selectedDate : null,
|
||||
);
|
||||
final String token =
|
||||
await context.read<RecoveryKeyCubit>().generateRecoveryKey(
|
||||
numberOfUses: _isAmountToggled
|
||||
? int.tryParse(_amountController.text)
|
||||
: null,
|
||||
expirationDate: _isExpirationToggled ? _selectedDate : null,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
|
@ -310,9 +313,10 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
enabled: _isAmountToggled,
|
||||
controller: _amountController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _isAmountError ? ' ' : null,
|
||||
labelText: 'recovery_key.key_amount_field_title'.tr(),),
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _isAmountError ? ' ' : null,
|
||||
labelText: 'recovery_key.key_amount_field_title'.tr(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
|
@ -352,9 +356,10 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
},
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _isExpirationError ? ' ' : null,
|
||||
labelText: 'recovery_key.key_duedate_field_title'.tr(),),
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _isExpirationError ? ' ' : null,
|
||||
labelText: 'recovery_key.key_duedate_field_title'.tr(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
|
@ -378,10 +383,11 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
|
||||
Future<DateTime> _selectDate(final BuildContext context) async {
|
||||
final DateTime? selected = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDate,
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime(DateTime.now().year + 50),);
|
||||
context: context,
|
||||
initialDate: _selectedDate,
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime(DateTime.now().year + 50),
|
||||
);
|
||||
|
||||
if (selected != null && selected != _selectedDate) {
|
||||
setState(
|
||||
|
|
|
@ -4,45 +4,45 @@ import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
|
|||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
|
||||
class RecoveryKeyReceiving extends StatelessWidget {
|
||||
const RecoveryKeyReceiving({required this.recoveryKey, super.key});
|
||||
const RecoveryKeyReceiving({required this.recoveryKey, final super.key});
|
||||
|
||||
final String recoveryKey;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => BrandHeroScreen(
|
||||
heroTitle: 'recovery_key.key_main_header'.tr(),
|
||||
heroSubtitle: 'recovery_key.key_receiving_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
children: [
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
recoveryKey,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 24,
|
||||
fontFamily: 'RobotoMono',
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.info_outlined, size: 24),
|
||||
const SizedBox(height: 16),
|
||||
Text('recovery_key.key_receiving_info'.tr()),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
title: 'recovery_key.key_receiving_done'.tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).popUntil((final route) => route.isFirst);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
heroTitle: 'recovery_key.key_main_header'.tr(),
|
||||
heroSubtitle: 'recovery_key.key_receiving_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
children: [
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
recoveryKey,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 24,
|
||||
fontFamily: 'RobotoMono',
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.info_outlined, size: 24),
|
||||
const SizedBox(height: 16),
|
||||
Text('recovery_key.key_receiving_info'.tr()),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
title: 'recovery_key.key_receiving_done'.tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).popUntil((final route) => route.isFirst);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'package:selfprivacy/ui/pages/users/users.dart';
|
|||
import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart';
|
||||
|
||||
class RootPage extends StatefulWidget {
|
||||
const RootPage({super.key});
|
||||
const RootPage({final super.key});
|
||||
|
||||
@override
|
||||
State<RootPage> createState() => _RootPageState();
|
||||
|
@ -92,7 +92,6 @@ class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
|||
}
|
||||
|
||||
class ChangeTab {
|
||||
|
||||
ChangeTab(this.onPress);
|
||||
final ValueChanged<int> onPress;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
part of 'server_details_screen.dart';
|
||||
|
||||
class _Chart extends StatelessWidget {
|
||||
const _Chart({final super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final HetznerMetricsCubit cubit = context.watch<HetznerMetricsCubit>();
|
||||
|
@ -129,44 +127,40 @@ class _Chart extends StatelessWidget {
|
|||
|
||||
class Legend extends StatelessWidget {
|
||||
const Legend({
|
||||
final Key? key,
|
||||
required this.color,
|
||||
required this.text,
|
||||
}) : super(key: key);
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final Color color;
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
_ColoredBox(color: color),
|
||||
const SizedBox(width: 5),
|
||||
BrandText.small(text),
|
||||
],
|
||||
);
|
||||
}
|
||||
Widget build(final BuildContext context) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
_ColoredBox(color: color),
|
||||
const SizedBox(width: 5),
|
||||
BrandText.small(text),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class _ColoredBox extends StatelessWidget {
|
||||
const _ColoredBox({
|
||||
final Key? key,
|
||||
required this.color,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
return Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
Widget build(final BuildContext context) => Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.3),
|
||||
border: Border.all(
|
||||
color: color,
|
||||
)),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'package:intl/intl.dart';
|
|||
|
||||
class CpuChart extends StatelessWidget {
|
||||
const CpuChart({
|
||||
Key? key,
|
||||
final Key? key,
|
||||
required this.data,
|
||||
required this.period,
|
||||
required this.start,
|
||||
|
@ -20,9 +20,9 @@ class CpuChart extends StatelessWidget {
|
|||
|
||||
List<FlSpot> getSpots() {
|
||||
var i = 0;
|
||||
List<FlSpot> res = [];
|
||||
final List<FlSpot> res = [];
|
||||
|
||||
for (var d in data) {
|
||||
for (final d in data) {
|
||||
res.add(FlSpot(i.toDouble(), d.value));
|
||||
i++;
|
||||
}
|
||||
|
@ -31,97 +31,96 @@ class CpuChart extends StatelessWidget {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(enabled: false),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: getSpots(),
|
||||
isCurved: true,
|
||||
barWidth: 1,
|
||||
color: Colors.red,
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
Widget build(final BuildContext context) => LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(enabled: false),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: getSpots(),
|
||||
isCurved: true,
|
||||
barWidth: 1,
|
||||
color: Colors.red,
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
minY: 0,
|
||||
maxY: 100,
|
||||
minX: data.length - 200,
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
interval: 20,
|
||||
reservedSize: 50,
|
||||
getTitlesWidget: (value, titleMeta) {
|
||||
return Padding(
|
||||
],
|
||||
minY: 0,
|
||||
maxY: 100,
|
||||
minX: data.length - 200,
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
interval: 20,
|
||||
reservedSize: 50,
|
||||
getTitlesWidget: (final value, final titleMeta) => Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: RotatedBox(
|
||||
quarterTurns: 1,
|
||||
child: Text(bottomTitle(value.toInt()),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.purple,
|
||||
fontWeight: FontWeight.bold,
|
||||
)),
|
||||
),
|
||||
);
|
||||
},
|
||||
showTitles: true,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
getTitlesWidget: (value, titleMeta) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 15),
|
||||
child: Text(
|
||||
value.toInt().toString(),
|
||||
style: progressTextStyleLight.copyWith(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? BrandColors.gray4
|
||||
: null,
|
||||
bottomTitle(value.toInt()),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.purple,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
));
|
||||
},
|
||||
interval: 25,
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
showTitles: true,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
getTitlesWidget: (final value, final titleMeta) => Padding(
|
||||
padding: const EdgeInsets.only(right: 15),
|
||||
child: Text(
|
||||
value.toInt().toString(),
|
||||
style: progressTextStyleLight.copyWith(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? BrandColors.gray4
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
interval: 25,
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(show: true),
|
||||
),
|
||||
gridData: FlGridData(show: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
bool checkToShowTitle(
|
||||
double minValue,
|
||||
double maxValue,
|
||||
SideTitles sideTitles,
|
||||
double appliedInterval,
|
||||
double value,
|
||||
final double minValue,
|
||||
final double maxValue,
|
||||
final SideTitles sideTitles,
|
||||
final double appliedInterval,
|
||||
final double value,
|
||||
) {
|
||||
if (value < 0) {
|
||||
return false;
|
||||
} else if (value == 0) {
|
||||
return true;
|
||||
}
|
||||
var localValue = value - minValue;
|
||||
var v = localValue / 20;
|
||||
|
||||
final localValue = value - minValue;
|
||||
final v = localValue / 20;
|
||||
return v - v.floor() == 0;
|
||||
}
|
||||
|
||||
String bottomTitle(int value) {
|
||||
String bottomTitle(final int value) {
|
||||
final hhmm = DateFormat('HH:mm');
|
||||
var day = DateFormat('MMMd');
|
||||
final day = DateFormat('MMMd');
|
||||
String res;
|
||||
|
||||
if (value <= 0) {
|
||||
return '';
|
||||
}
|
||||
var time = data[value].time;
|
||||
|
||||
final time = data[value].time;
|
||||
switch (period) {
|
||||
case Period.hour:
|
||||
case Period.day:
|
||||
|
|
|
@ -2,60 +2,57 @@ part of 'server_details_screen.dart';
|
|||
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({
|
||||
Key? key,
|
||||
required this.providerState,
|
||||
required this.tabController,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final StateType providerState;
|
||||
final TabController tabController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
IconStatusMask(
|
||||
status: providerState,
|
||||
child: const Icon(
|
||||
BrandIcons.server,
|
||||
size: 40,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
BrandText.h2('providers.server.card_title'.tr()),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 2,
|
||||
),
|
||||
child: PopupMenuButton<_PopupMenuItemType>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
Widget build(final BuildContext context) => Row(
|
||||
children: [
|
||||
IconStatusMask(
|
||||
status: providerState,
|
||||
child: const Icon(
|
||||
BrandIcons.server,
|
||||
size: 40,
|
||||
color: Colors.white,
|
||||
),
|
||||
onSelected: (_PopupMenuItemType result) {
|
||||
switch (result) {
|
||||
case _PopupMenuItemType.setting:
|
||||
tabController.animateTo(1);
|
||||
break;
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (BuildContext context) => [
|
||||
PopupMenuItem<_PopupMenuItemType>(
|
||||
value: _PopupMenuItemType.setting,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 5),
|
||||
child: Text('basis.settings'.tr()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
const SizedBox(width: 10),
|
||||
BrandText.h2('providers.server.card_title'.tr()),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 2,
|
||||
),
|
||||
child: PopupMenuButton<_PopupMenuItemType>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
onSelected: (final _PopupMenuItemType result) {
|
||||
switch (result) {
|
||||
case _PopupMenuItemType.setting:
|
||||
tabController.animateTo(1);
|
||||
break;
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (final BuildContext context) => [
|
||||
PopupMenuItem<_PopupMenuItemType>(
|
||||
value: _PopupMenuItemType.setting,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 5),
|
||||
child: Text('basis.settings'.tr()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
enum _PopupMenuItemType { setting }
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'package:intl/intl.dart';
|
|||
|
||||
class NetworkChart extends StatelessWidget {
|
||||
const NetworkChart({
|
||||
Key? key,
|
||||
final Key? key,
|
||||
required this.listData,
|
||||
required this.period,
|
||||
required this.start,
|
||||
|
@ -20,11 +20,11 @@ class NetworkChart extends StatelessWidget {
|
|||
final Period period;
|
||||
final DateTime start;
|
||||
|
||||
List<FlSpot> getSpots(data) {
|
||||
List<FlSpot> getSpots(final data) {
|
||||
var i = 0;
|
||||
List<FlSpot> res = [];
|
||||
final List<FlSpot> res = [];
|
||||
|
||||
for (var d in data) {
|
||||
for (final d in data) {
|
||||
res.add(FlSpot(i.toDouble(), d.value));
|
||||
i++;
|
||||
}
|
||||
|
@ -33,120 +33,119 @@ class NetworkChart extends StatelessWidget {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 150,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(enabled: false),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: getSpots(listData[0]),
|
||||
isCurved: true,
|
||||
barWidth: 1,
|
||||
color: Colors.red,
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
Widget build(final BuildContext context) => SizedBox(
|
||||
height: 150,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(enabled: false),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: getSpots(listData[0]),
|
||||
isCurved: true,
|
||||
barWidth: 1,
|
||||
color: Colors.red,
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
LineChartBarData(
|
||||
spots: getSpots(listData[1]),
|
||||
isCurved: true,
|
||||
barWidth: 1,
|
||||
color: Colors.green,
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
LineChartBarData(
|
||||
spots: getSpots(listData[1]),
|
||||
isCurved: true,
|
||||
barWidth: 1,
|
||||
color: Colors.green,
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
minY: 0,
|
||||
maxY: [
|
||||
...listData[0].map((e) => e.value),
|
||||
...listData[1].map((e) => e.value)
|
||||
].reduce(max) *
|
||||
1.2,
|
||||
minX: listData[0].length - 200,
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
interval: 20,
|
||||
reservedSize: 50,
|
||||
getTitlesWidget: (value, titleMeta) {
|
||||
return Padding(
|
||||
],
|
||||
minY: 0,
|
||||
maxY: [
|
||||
...listData[0].map((final e) => e.value),
|
||||
...listData[1].map((final e) => e.value)
|
||||
].reduce(max) *
|
||||
1.2,
|
||||
minX: listData[0].length - 200,
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
interval: 20,
|
||||
reservedSize: 50,
|
||||
getTitlesWidget: (final value, final titleMeta) => Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: RotatedBox(
|
||||
quarterTurns: 1,
|
||||
child: Text(bottomTitle(value.toInt()),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.purple,
|
||||
fontWeight: FontWeight.bold,
|
||||
)),
|
||||
),
|
||||
);
|
||||
},
|
||||
showTitles: true,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
reservedSize: 50,
|
||||
getTitlesWidget: (value, titleMeta) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: Text(
|
||||
value.toInt().toString(),
|
||||
style: progressTextStyleLight.copyWith(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? BrandColors.gray4
|
||||
: null,
|
||||
bottomTitle(value.toInt()),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.purple,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
));
|
||||
},
|
||||
interval: [
|
||||
...listData[0].map((e) => e.value),
|
||||
...listData[1].map((e) => e.value)
|
||||
].reduce(max) *
|
||||
2 /
|
||||
10,
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
showTitles: true,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
reservedSize: 50,
|
||||
getTitlesWidget: (final value, final titleMeta) => Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: Text(
|
||||
value.toInt().toString(),
|
||||
style: progressTextStyleLight.copyWith(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? BrandColors.gray4
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
interval: [
|
||||
...listData[0].map((final e) => e.value),
|
||||
...listData[1].map((final e) => e.value)
|
||||
].reduce(max) *
|
||||
2 /
|
||||
10,
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(show: true),
|
||||
),
|
||||
gridData: FlGridData(show: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
bool checkToShowTitle(
|
||||
double minValue,
|
||||
double maxValue,
|
||||
SideTitles sideTitles,
|
||||
double appliedInterval,
|
||||
double value,
|
||||
final double minValue,
|
||||
final double maxValue,
|
||||
final SideTitles sideTitles,
|
||||
final double appliedInterval,
|
||||
final double value,
|
||||
) {
|
||||
if (value < 0) {
|
||||
return false;
|
||||
} else if (value == 0) {
|
||||
return true;
|
||||
}
|
||||
var diff = value - minValue;
|
||||
var finalValue = diff / 20;
|
||||
|
||||
final diff = value - minValue;
|
||||
final finalValue = diff / 20;
|
||||
return finalValue - finalValue.floor() == 0;
|
||||
}
|
||||
|
||||
String bottomTitle(int value) {
|
||||
String bottomTitle(final int value) {
|
||||
final hhmm = DateFormat('HH:mm');
|
||||
var day = DateFormat('MMMd');
|
||||
final day = DateFormat('MMMd');
|
||||
String res;
|
||||
|
||||
if (value <= 0) {
|
||||
return '';
|
||||
}
|
||||
var time = listData[0][value].time;
|
||||
|
||||
final time = listData[0][value].time;
|
||||
switch (period) {
|
||||
case Period.hour:
|
||||
case Period.day:
|
||||
|
|
|
@ -22,8 +22,8 @@ import 'package:selfprivacy/utils/named_font_weight.dart';
|
|||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:timezone/timezone.dart';
|
||||
|
||||
import 'cpu_chart.dart';
|
||||
import 'network_charts.dart';
|
||||
import 'package:selfprivacy/ui/pages/server_details/cpu_chart.dart';
|
||||
import 'package:selfprivacy/ui/pages/server_details/network_charts.dart';
|
||||
|
||||
part 'chart.dart';
|
||||
part 'header.dart';
|
||||
|
@ -34,7 +34,7 @@ part 'time_zone/time_zone.dart';
|
|||
var navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class ServerDetailsScreen extends StatefulWidget {
|
||||
const ServerDetailsScreen({Key? key}) : super(key: key);
|
||||
const ServerDetailsScreen({final super.key});
|
||||
|
||||
@override
|
||||
State<ServerDetailsScreen> createState() => _ServerDetailsScreenState();
|
||||
|
@ -60,13 +60,13 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isReady = context.watch<ServerInstallationCubit>().state
|
||||
Widget build(final BuildContext context) {
|
||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
var providerState = isReady ? StateType.stable : StateType.uninitialized;
|
||||
final providerState = isReady ? StateType.stable : StateType.uninitialized;
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => ServerDetailsCubit()..check(),
|
||||
create: (final context) => ServerDetailsCubit()..check(),
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
|
@ -97,19 +97,20 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_Header(
|
||||
providerState: providerState,
|
||||
tabController: tabController),
|
||||
providerState: providerState,
|
||||
tabController: tabController,
|
||||
),
|
||||
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BlocProvider(
|
||||
create: (context) => HetznerMetricsCubit()..restart(),
|
||||
child: const _Chart(),
|
||||
create: (final context) => HetznerMetricsCubit()..restart(),
|
||||
child: _Chart(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const _TextDetails(),
|
||||
_TextDetails(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue