Merge branch 'master' into fix/app-bars

pull/132/head
Inex Code 2022-10-06 10:40:06 +03:00
commit 3f663b7900
16 changed files with 96 additions and 220 deletions

View File

@ -45,4 +45,6 @@ flatpak build-bundle flatpak-repo org.selfprivacy.app.flatpak org.selfprivacy.ap
## Translations
[![Translation status](http://weblate.selfprivacy.org/widgets/selfprivacy/-/selfprivacy-app/multi-auto.svg)](http://weblate.selfprivacy.org/engage/selfprivacy/)
Translations are stored in `assets/translations/*.json` and can be edited on <https://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/>.

View File

@ -110,6 +110,9 @@
"location": "Location",
"core_count": {
"one": "{} core",
"two": "{} cores",
"few": "{} cores",
"many": "{} cores",
"other": "{} cores"
}
},
@ -182,7 +185,7 @@
},
"not_ready_card": {
"begin": "Please finish application setup using ",
"insertion": "@:more.configuration_wizard",
"insertion": "Setup Wizard",
"end": " for further work",
"in_menu": "Server is not set up yet. Please finish setup using setup wizard for further work."
},

View File

@ -185,7 +185,7 @@
},
"not_ready_card": {
"begin": "Завершите настройку приложения используя ",
"insertion": "@:more.configuration_wizard",
"insertion": "Мастер Настройки",
"end": " для продолжения работы",
"in_menu": "Сервер ещё не настроен, воспользуйтесь мастером подключения."
},

View File

@ -13,6 +13,7 @@ import 'package:selfprivacy/logic/models/json/api_token.dart';
import 'package:selfprivacy/logic/models/json/backup.dart';
import 'package:selfprivacy/logic/models/json/device_token.dart';
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/logic/models/timezone_settings.dart';
class ApiResponse<D> {
@ -380,13 +381,13 @@ class ServerApi extends ApiMap {
}
Future<void> switchService(
final ServiceTypes type,
final Service service,
final bool needToTurnOn,
) async {
final Dio client = await getClient();
try {
client.post(
'/services/${type.url}/${needToTurnOn ? 'enable' : 'disable'}',
'/services/${service.id}/${needToTurnOn ? 'enable' : 'disable'}',
);
} on DioError catch (e) {
print(e.message);
@ -395,7 +396,7 @@ class ServerApi extends ApiMap {
}
}
Future<Map<ServiceTypes, bool>> servicesPowerCheck() async {
Future<Map<String, bool>> servicesPowerCheck() async {
Response response;
final Dio client = await getClient();
@ -409,11 +410,11 @@ class ServerApi extends ApiMap {
}
return {
ServiceTypes.bitwarden: response.data['bitwarden'] == 0,
ServiceTypes.gitea: response.data['gitea'] == 0,
ServiceTypes.nextcloud: response.data['nextcloud'] == 0,
ServiceTypes.ocserv: response.data['ocserv'] == 0,
ServiceTypes.pleroma: response.data['pleroma'] == 0,
'bitwarden': response.data['bitwarden'] == 0,
'gitea': response.data['gitea'] == 0,
'nextcloud': response.data['nextcloud'] == 0,
'ocserv': response.data['ocserv'] == 0,
'pleroma': response.data['pleroma'] == 0,
};
}
@ -867,28 +868,3 @@ class ServerApi extends ApiMap {
return ApiResponse(statusCode: code, data: null);
}
}
extension UrlServerExt on ServiceTypes {
String get url {
switch (this) {
// case ServiceTypes.mail:
// return ''; // cannot be switch off
// case ServiceTypes.messenger:
// return ''; // external service
// case ServiceTypes.video:
// return ''; // jitsi meet not working
case ServiceTypes.bitwarden:
return 'bitwarden';
case ServiceTypes.nextcloud:
return 'nextcloud';
case ServiceTypes.pleroma:
return 'pleroma';
case ServiceTypes.gitea:
return 'gitea';
case ServiceTypes.ocserv:
return 'ocserv';
default:
throw Exception('wrong state');
}
}
}

View File

@ -1,7 +1,3 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
enum LoadingStatus {
uninitialized,
refreshing,
@ -36,111 +32,3 @@ enum Period {
}
}
}
enum ServiceTypes {
mailserver,
bitwarden,
jitsi,
nextcloud,
pleroma,
gitea,
ocserv,
}
extension ServiceTypesExt on ServiceTypes {
String get title {
switch (this) {
case ServiceTypes.mailserver:
return 'mail.title'.tr();
case ServiceTypes.bitwarden:
return 'password_manager.title'.tr();
case ServiceTypes.jitsi:
return 'video.title'.tr();
case ServiceTypes.nextcloud:
return 'cloud.title'.tr();
case ServiceTypes.pleroma:
return 'social_network.title'.tr();
case ServiceTypes.gitea:
return 'git.title'.tr();
case ServiceTypes.ocserv:
return 'vpn.title'.tr();
}
}
String get subtitle {
switch (this) {
case ServiceTypes.mailserver:
return 'mail.subtitle'.tr();
case ServiceTypes.bitwarden:
return 'password_manager.subtitle'.tr();
case ServiceTypes.jitsi:
return 'video.subtitle'.tr();
case ServiceTypes.nextcloud:
return 'cloud.subtitle'.tr();
case ServiceTypes.pleroma:
return 'social_network.subtitle'.tr();
case ServiceTypes.gitea:
return 'git.subtitle'.tr();
case ServiceTypes.ocserv:
return 'vpn.subtitle'.tr();
}
}
String get loginInfo {
switch (this) {
case ServiceTypes.mailserver:
return 'mail.login_info'.tr();
case ServiceTypes.bitwarden:
return 'password_manager.login_info'.tr();
case ServiceTypes.jitsi:
return 'video.login_info'.tr();
case ServiceTypes.nextcloud:
return 'cloud.login_info'.tr();
case ServiceTypes.pleroma:
return 'social_network.login_info'.tr();
case ServiceTypes.gitea:
return 'git.login_info'.tr();
case ServiceTypes.ocserv:
return '';
}
}
String get subdomain {
switch (this) {
case ServiceTypes.bitwarden:
return 'password';
case ServiceTypes.jitsi:
return 'meet';
case ServiceTypes.nextcloud:
return 'cloud';
case ServiceTypes.pleroma:
return 'social';
case ServiceTypes.gitea:
return 'git';
case ServiceTypes.ocserv:
default:
return '';
}
}
IconData get icon {
switch (this) {
case ServiceTypes.mailserver:
return BrandIcons.envelope;
case ServiceTypes.bitwarden:
return BrandIcons.key;
case ServiceTypes.jitsi:
return BrandIcons.webcam;
case ServiceTypes.nextcloud:
return BrandIcons.upload;
case ServiceTypes.pleroma:
return BrandIcons.social;
case ServiceTypes.gitea:
return BrandIcons.git;
case ServiceTypes.ocserv:
return Icons.vpn_lock_outlined;
}
}
String get txt => toString().split('.')[1];
}

View File

@ -45,10 +45,10 @@ class JobsCubit extends Cubit<JobsState> {
newJobsList.addAll((state as JobsStateWithJobs).clientJobList);
}
final bool needToRemoveJob = newJobsList
.any((final el) => el is ServiceToggleJob && el.type == job.type);
.any((final el) => el is ServiceToggleJob && el.id == job.id);
if (needToRemoveJob) {
final ClientJob removingJob = newJobsList.firstWhere(
(final el) => el is ServiceToggleJob && el.type == job.type,
(final el) => el is ServiceToggleJob && el.id == job.id,
);
removeJob(removingJob.id);
} else {
@ -114,7 +114,7 @@ class JobsCubit extends Cubit<JobsState> {
}
if (job is ServiceToggleJob) {
hasServiceJobs = true;
await api.switchService(job.type.name, job.needToTurnOn);
await api.switchService(job.service.id, job.needToTurnOn);
}
if (job is CreateSSHKeyJob) {
await usersCubit.addSshKey(job.user, job.publicKey);

View File

@ -15,7 +15,6 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_
import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
@ -571,7 +570,7 @@ class ServerInstallationRepository {
);
final String serverIp = await getServerIpFromDomain(serverDomain);
if (recoveryCapabilities == ServerRecoveryCapabilities.legacy) {
final Map<ServiceTypes, bool> apiResponse =
final Map<String, bool> apiResponse =
await serverApi.servicesPowerCheck();
if (apiResponse.isNotEmpty) {
return ServerHostingDetails(

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/service.dart';

View File

@ -63,17 +63,17 @@ class ServicesState extends ServerInstallationDependendState {
lockedServices,
];
bool isEnableByType(final ServiceTypes type) {
switch (type) {
case ServiceTypes.bitwarden:
bool isEnableByType(final Service service) {
switch (service.id) {
case 'bitwarden':
return isPasswordManagerEnable;
case ServiceTypes.nextcloud:
case 'nextcloud':
return isCloudEnable;
case ServiceTypes.pleroma:
case 'pleroma':
return isSocialNetworkEnable;
case ServiceTypes.gitea:
case 'gitea':
return isGitEnable;
case ServiceTypes.ocserv:
case 'ocserv':
return isVpnEnable;
default:
throw Exception('wrong state');

View File

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/utils/password_generator.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
@ -62,23 +63,23 @@ class DeleteUserJob extends ClientJob {
class ToggleJob extends ClientJob {
ToggleJob({
required this.type,
required final this.service,
required final super.title,
});
final ServiceTypes type;
final Service service;
@override
List<Object> get props => [...super.props, type];
List<Object> get props => [...super.props, service];
}
class ServiceToggleJob extends ToggleJob {
ServiceToggleJob({
required final super.type,
required super.service,
required this.needToTurnOn,
}) : super(
title:
'${needToTurnOn ? "jobs.service_turn_on".tr() : "jobs.service_turn_off".tr()} ${type.title}',
'${needToTurnOn ? "jobs.service_turn_on".tr() : "jobs.service_turn_off".tr()} ${service.displayName}',
);
final bool needToTurnOn;

View File

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart';
import 'package:selfprivacy/logic/models/disk_size.dart';
@ -20,6 +21,25 @@ class Service {
this.url,
});
/// TODO Turn loginInfo into dynamic data, not static!
String get loginInfo {
switch (id) {
case 'mailserver':
return 'mail.login_info'.tr();
case 'bitwarden':
return 'password_manager.login_info'.tr();
case 'jitsi':
return 'video.login_info'.tr();
case 'nextcloud':
return 'cloud.login_info'.tr();
case 'pleroma':
return 'social_network.login_info'.tr();
case 'gitea':
return 'git.login_info'.tr();
}
return '';
}
Service.fromGraphQL(final Query$AllServices$services$allServices service)
: this(
id: service.id,

View File

@ -4,11 +4,11 @@ import 'package:selfprivacy/logic/models/state_types.dart';
class IconStatusMask extends StatelessWidget {
const IconStatusMask({
required this.child,
required this.icon,
required this.status,
final super.key,
});
final Icon child;
final Widget icon;
final StateType status;
@ -42,7 +42,7 @@ class IconStatusMask extends StatelessWidget {
colors: colors,
tileMode: TileMode.mirror,
).createShader(bounds),
child: child,
child: icon,
);
}
}

View File

@ -148,12 +148,12 @@ class _Card extends StatelessWidget {
children: [
IconStatusMask(
status: state,
child: Icon(icon, size: 30, color: Colors.white),
icon: Icon(icon, size: 30, color: Colors.white),
),
if (state != StateType.uninitialized)
IconStatusMask(
status: state,
child: Icon(
icon: Icon(
state == StateType.stable
? Icons.check_circle_outline
: state == StateType.warning

View File

@ -80,7 +80,7 @@ class StorageCard extends StatelessWidget {
if (state != StateType.uninitialized)
IconStatusMask(
status: state,
child: Icon(
icon: Icon(
diskStatus.isDiskOkay
? Icons.check_circle_outline
: Icons.error_outline,

View File

@ -91,7 +91,7 @@ class _ServicePageState extends State<ServicePage> {
onTap: () => {
context.read<JobsCubit>().createOrRemoveServiceToggleJob(
ServiceToggleJob(
type: _idToLegacyType(service.id),
service: service,
needToTurnOn: serviceDisabled,
),
),
@ -142,28 +142,6 @@ class _ServicePageState extends State<ServicePage> {
],
);
}
// TODO: Get rid as soon as possible
ServiceTypes _idToLegacyType(final String serviceId) {
switch (serviceId) {
case 'mailserver':
return ServiceTypes.mailserver;
case 'jitsi':
return ServiceTypes.jitsi;
case 'bitwarden':
return ServiceTypes.bitwarden;
case 'nextcloud':
return ServiceTypes.nextcloud;
case 'pleroma':
return ServiceTypes.pleroma;
case 'gitea':
return ServiceTypes.gitea;
case 'ocserv':
return ServiceTypes.ocserv;
default:
throw Exception('wrong state');
}
}
}
class ServiceStatusCard extends StatelessWidget {

View File

@ -1,12 +1,14 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/models/job.dart';
import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
@ -21,11 +23,11 @@ import 'package:selfprivacy/utils/ui_helpers.dart';
import 'package:url_launcher/url_launcher.dart';
const switchableServices = [
ServiceTypes.bitwarden,
ServiceTypes.nextcloud,
ServiceTypes.pleroma,
ServiceTypes.gitea,
ServiceTypes.ocserv,
'bitwarden',
'nextcloud',
'pleroma',
'gitea',
'ocserv',
];
class ServicesPage extends StatefulWidget {
@ -70,13 +72,16 @@ class _ServicesPageState extends State<ServicesPage> {
BrandText.body1('basis.services_title'.tr()),
const SizedBox(height: 24),
if (!isReady) ...[const NotReadyCard(), const SizedBox(height: 24)],
...ServiceTypes.values
...context
.read<ServicesCubit>()
.state
.services
.map(
(final t) => Padding(
(final service) => Padding(
padding: const EdgeInsets.only(
bottom: 30,
),
child: _Card(serviceType: t),
child: _Card(service: service),
),
)
.toList()
@ -88,9 +93,9 @@ class _ServicesPageState extends State<ServicesPage> {
}
class _Card extends StatelessWidget {
const _Card({required this.serviceType});
const _Card({required this.service});
final ServiceTypes serviceType;
final Service service;
@override
Widget build(final BuildContext context) {
final isReady = context.watch<ServerInstallationCubit>().state
@ -100,16 +105,16 @@ class _Card extends StatelessWidget {
final jobsCubit = context.watch<JobsCubit>();
final jobState = jobsCubit.state;
final switchableService = switchableServices.contains(serviceType);
final switchableService = switchableServices.contains(service.id);
final hasSwitchJob = switchableService &&
jobState is JobsStateWithJobs &&
jobState.clientJobList.any(
(final el) => el is ServiceToggleJob && el.type == serviceType,
(final el) => el is ServiceToggleJob && el.id == service.id,
);
final isSwitchOn = isReady &&
(!switchableServices.contains(serviceType) ||
serviceState.isEnableByType(serviceType));
(!switchableServices.contains(service.id) ||
serviceState.isEnableByType(service));
final config = context.watch<ServerInstallationCubit>().state;
final domainName = UiHelpers.getDomainName(config);
@ -117,7 +122,7 @@ class _Card extends StatelessWidget {
return GestureDetector(
onTap: isReady
? () => Navigator.of(context)
.push(materialRoute(ServicePage(serviceId: serviceType.name)))
.push(materialRoute(ServicePage(serviceId: service.id)))
: null,
child: BrandCards.big(
child: Column(
@ -128,7 +133,12 @@ class _Card extends StatelessWidget {
IconStatusMask(
status:
isSwitchOn ? StateType.stable : StateType.uninitialized,
child: Icon(serviceType.icon, size: 30, color: Colors.white),
icon: SvgPicture.string(
service.svgIcon,
width: 30.0,
height: 30.0,
color: Theme.of(context).colorScheme.onBackground,
),
),
if (isReady && switchableService) ...[
const Spacer(),
@ -138,11 +148,11 @@ class _Card extends StatelessWidget {
if (hasSwitchJob) {
isActive = (jobState.clientJobList.firstWhere(
(final el) =>
el is ServiceToggleJob && el.type == serviceType,
el is ServiceToggleJob && el.id == service.id,
) as ServiceToggleJob)
.needToTurnOn;
} else {
isActive = serviceState.isEnableByType(serviceType);
isActive = serviceState.isEnableByType(service);
}
return BrandSwitch(
@ -150,7 +160,7 @@ class _Card extends StatelessWidget {
onChanged: (final value) =>
jobsCubit.createOrRemoveServiceToggleJob(
ServiceToggleJob(
type: serviceType,
service: service,
needToTurnOn: value,
),
),
@ -167,17 +177,17 @@ class _Card extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
BrandText.h2(serviceType.title),
BrandText.h2(service.displayName),
const SizedBox(height: 10),
if (serviceType.subdomain != '')
if (service.url != '' && service.url != null)
Column(
children: [
GestureDetector(
onTap: () => _launchURL(
'https://${serviceType.subdomain}.$domainName',
'https://${service.url}',
),
child: Text(
'${serviceType.subdomain}.$domainName',
'${service.url}',
style: TextStyle(
color:
Theme.of(context).colorScheme.secondary,
@ -188,7 +198,7 @@ class _Card extends StatelessWidget {
const SizedBox(height: 10),
],
),
if (serviceType == ServiceTypes.mailserver)
if (service.id == 'mailserver')
Column(
children: [
Text(
@ -201,9 +211,9 @@ class _Card extends StatelessWidget {
const SizedBox(height: 10),
],
),
BrandText.body2(serviceType.loginInfo),
BrandText.body2(service.loginInfo),
const SizedBox(height: 10),
BrandText.body2(serviceType.subtitle),
BrandText.body2(service.description),
const SizedBox(height: 10),
],
),