Co-authored-by: Inex Code <inex.code@selfprivacy.org>
pull/90/head
NaiJi ✨ 2022-06-06 01:40:34 +03:00
parent 4db0413c42
commit 2ac8e4366b
128 changed files with 3773 additions and 3419 deletions

View File

@ -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

View File

@ -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),

View File

@ -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(

View File

@ -1,5 +1,3 @@
// ignore_for_file: always_specify_types
import 'package:flutter/material.dart';
class BrandColors {

View File

@ -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!,
);
}

View File

@ -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(

View File

@ -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}');
}

View File

@ -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';

View File

@ -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',

View File

@ -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();
}

View File

@ -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';

View File

@ -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}) {

View File

@ -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,

View File

@ -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));
}

View File

@ -1,5 +1,3 @@
// ignore_for_file: always_specify_types
part of 'backups_cubit.dart';
class BackupsState extends ServerInstallationDependendState {

View File

@ -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;
}
}

View File

@ -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];

View File

@ -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 [];
}

View File

@ -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,
);
}

View File

@ -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;
}

View File

@ -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';

View File

@ -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)
],
);

View File

@ -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)
],
);

View File

@ -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();

View File

@ -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]);

View File

@ -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

View File

@ -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(),
),
],
);

View File

@ -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]);
}

View File

@ -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()]),
);
}

View File

@ -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();
}

View File

@ -1,5 +1,3 @@
// ignore_for_file: always_specify_types
part of 'hetzner_metrics_cubit.dart';
abstract class HetznerMetricsState extends Equatable {

View File

@ -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());

View File

@ -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();

View File

@ -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;

View File

@ -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,
);
}

View File

@ -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,

View File

@ -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);
}

View File

@ -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,
);
}

View File

@ -1,5 +1,3 @@
// ignore_for_file: always_specify_types
part of 'services_cubit.dart';
class ServicesState extends ServerInstallationDependendState {

View File

@ -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...'),
),
);
}
}

View File

@ -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;
}

View File

@ -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';

View File

@ -1,5 +1,3 @@
// ignore_for_file: always_specify_types
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/models/message.dart';

View File

@ -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';
}

View File

@ -1,7 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: always_specify_types
part of 'user.dart';
// **************************************************************************

View File

@ -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';

View File

@ -1,5 +1,3 @@
// ignore_for_file: always_specify_types
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';

View File

@ -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;

View File

@ -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({

View File

@ -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({

View File

@ -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 {

View File

@ -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';

View File

@ -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,

View File

@ -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,
};
}

View File

@ -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!;
},
),
),
),
),
),
);
);
}

View File

@ -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);
}

View File

@ -24,7 +24,7 @@ class ActionButton extends StatelessWidget {
),
onPressed: () {
navigator.pop();
if (onPressed != null) onPressed!();
onPressed?.call();
},
);
}

View File

@ -1,5 +1,3 @@
// ignore_for_file: always_specify_types
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';

View File

@ -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,
)
],
),
),
),
),
);
);
}

View File

@ -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;

View File

@ -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,
);
}

View File

@ -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(),
],
),
);
),
);
}

View File

@ -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,
],
),
),
),
);
);
}

View File

@ -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),
],
);
}

View File

@ -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,
);
}

View File

@ -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)
],
),
),
),
);
);
}

View File

@ -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 {

View File

@ -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,
);
}

View File

@ -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,
);
}

View File

@ -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,

View File

@ -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(() {

View File

@ -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;

View File

@ -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
],
);
},
);
}

View File

@ -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),
),
],
),
),
),
);
);
}

View File

@ -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(),
),
),
),
),
);
}

View File

@ -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),
],
),
);
}

View File

@ -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,
),
);
},),
),
);
},
),
),
);
}

View File

@ -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,
),
);
},
),
),
);
}

View File

@ -18,5 +18,5 @@ class PreStyledButtons {
}) =>
_CloseButton(onPress: onPress);
static Widget flash() => const _BrandFlashButton();
static Widget flash() => _BrandFlashButton();
}

View File

@ -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)
],
),

View File

@ -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,
),
],
),
);
}

View File

@ -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(),
),
],
),

View File

@ -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();
},
),
],
),
);
}

View File

@ -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),
],
);
}

View File

@ -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);

View File

@ -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',
),
),
);
);
}

View File

@ -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)),
),
],
);
}

View File

@ -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(),
],
);
}
},
),
),
),
);
);
}

View File

@ -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();

View File

@ -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,

View File

@ -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({

View File

@ -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(

View File

@ -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(

View File

@ -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);
},
),
],
);
}

View File

@ -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;
}

View File

@ -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,
)),
);
}
),
),
);
}

View File

@ -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:

View File

@ -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 }

View File

@ -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:

View File

@ -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