mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-26 18:56:38 +00:00
Rewrite services cubit and add basic service screen.
This commit is contained in:
parent
62929a4839
commit
7d8f8e1d38
|
@ -37,7 +37,8 @@ class GenericJobMutationReturn extends GenericMutationResult {
|
|||
final ServerJob? job;
|
||||
}
|
||||
|
||||
class ServerApi extends ApiMap with VolumeApi, JobsApi, ServerActionsApi {
|
||||
class ServerApi extends ApiMap
|
||||
with VolumeApi, JobsApi, ServerActionsApi, ServicesApi {
|
||||
ServerApi({
|
||||
this.hasLogger = false,
|
||||
this.isWithToken = true,
|
||||
|
|
|
@ -409,11 +409,11 @@ class ServerApi extends ApiMap {
|
|||
}
|
||||
|
||||
return {
|
||||
ServiceTypes.passwordManager: response.data['bitwarden'] == 0,
|
||||
ServiceTypes.git: response.data['gitea'] == 0,
|
||||
ServiceTypes.cloud: response.data['nextcloud'] == 0,
|
||||
ServiceTypes.vpn: response.data['ocserv'] == 0,
|
||||
ServiceTypes.socialNetwork: response.data['pleroma'] == 0,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -907,15 +907,15 @@ extension UrlServerExt on ServiceTypes {
|
|||
// return ''; // external service
|
||||
// case ServiceTypes.video:
|
||||
// return ''; // jitsi meet not working
|
||||
case ServiceTypes.passwordManager:
|
||||
case ServiceTypes.bitwarden:
|
||||
return 'bitwarden';
|
||||
case ServiceTypes.cloud:
|
||||
case ServiceTypes.nextcloud:
|
||||
return 'nextcloud';
|
||||
case ServiceTypes.socialNetwork:
|
||||
case ServiceTypes.pleroma:
|
||||
return 'pleroma';
|
||||
case ServiceTypes.git:
|
||||
case ServiceTypes.gitea:
|
||||
return 'gitea';
|
||||
case ServiceTypes.vpn:
|
||||
case ServiceTypes.ocserv:
|
||||
return 'ocserv';
|
||||
default:
|
||||
throw Exception('wrong state');
|
||||
|
|
|
@ -23,94 +23,86 @@ enum InitializingSteps {
|
|||
enum Period { hour, day, month }
|
||||
|
||||
enum ServiceTypes {
|
||||
mail,
|
||||
messenger,
|
||||
passwordManager,
|
||||
video,
|
||||
cloud,
|
||||
socialNetwork,
|
||||
git,
|
||||
vpn,
|
||||
mailserver,
|
||||
bitwarden,
|
||||
jitsi,
|
||||
nextcloud,
|
||||
pleroma,
|
||||
gitea,
|
||||
ocserv,
|
||||
}
|
||||
|
||||
extension ServiceTypesExt on ServiceTypes {
|
||||
String get title {
|
||||
switch (this) {
|
||||
case ServiceTypes.mail:
|
||||
case ServiceTypes.mailserver:
|
||||
return 'services.mail.title'.tr();
|
||||
case ServiceTypes.messenger:
|
||||
return 'services.messenger.title'.tr();
|
||||
case ServiceTypes.passwordManager:
|
||||
case ServiceTypes.bitwarden:
|
||||
return 'services.password_manager.title'.tr();
|
||||
case ServiceTypes.video:
|
||||
case ServiceTypes.jitsi:
|
||||
return 'services.video.title'.tr();
|
||||
case ServiceTypes.cloud:
|
||||
case ServiceTypes.nextcloud:
|
||||
return 'services.cloud.title'.tr();
|
||||
case ServiceTypes.socialNetwork:
|
||||
case ServiceTypes.pleroma:
|
||||
return 'services.social_network.title'.tr();
|
||||
case ServiceTypes.git:
|
||||
case ServiceTypes.gitea:
|
||||
return 'services.git.title'.tr();
|
||||
case ServiceTypes.vpn:
|
||||
case ServiceTypes.ocserv:
|
||||
return 'services.vpn.title'.tr();
|
||||
}
|
||||
}
|
||||
|
||||
String get subtitle {
|
||||
switch (this) {
|
||||
case ServiceTypes.mail:
|
||||
case ServiceTypes.mailserver:
|
||||
return 'services.mail.subtitle'.tr();
|
||||
case ServiceTypes.messenger:
|
||||
return 'services.messenger.subtitle'.tr();
|
||||
case ServiceTypes.passwordManager:
|
||||
case ServiceTypes.bitwarden:
|
||||
return 'services.password_manager.subtitle'.tr();
|
||||
case ServiceTypes.video:
|
||||
case ServiceTypes.jitsi:
|
||||
return 'services.video.subtitle'.tr();
|
||||
case ServiceTypes.cloud:
|
||||
case ServiceTypes.nextcloud:
|
||||
return 'services.cloud.subtitle'.tr();
|
||||
case ServiceTypes.socialNetwork:
|
||||
case ServiceTypes.pleroma:
|
||||
return 'services.social_network.subtitle'.tr();
|
||||
case ServiceTypes.git:
|
||||
case ServiceTypes.gitea:
|
||||
return 'services.git.subtitle'.tr();
|
||||
case ServiceTypes.vpn:
|
||||
case ServiceTypes.ocserv:
|
||||
return 'services.vpn.subtitle'.tr();
|
||||
}
|
||||
}
|
||||
|
||||
String get loginInfo {
|
||||
switch (this) {
|
||||
case ServiceTypes.mail:
|
||||
case ServiceTypes.mailserver:
|
||||
return 'services.mail.login_info'.tr();
|
||||
case ServiceTypes.messenger:
|
||||
return 'services.messenger.login_info'.tr();
|
||||
case ServiceTypes.passwordManager:
|
||||
case ServiceTypes.bitwarden:
|
||||
return 'services.password_manager.login_info'.tr();
|
||||
case ServiceTypes.video:
|
||||
case ServiceTypes.jitsi:
|
||||
return 'services.video.login_info'.tr();
|
||||
case ServiceTypes.cloud:
|
||||
case ServiceTypes.nextcloud:
|
||||
return 'services.cloud.login_info'.tr();
|
||||
case ServiceTypes.socialNetwork:
|
||||
case ServiceTypes.pleroma:
|
||||
return 'services.social_network.login_info'.tr();
|
||||
case ServiceTypes.git:
|
||||
case ServiceTypes.gitea:
|
||||
return 'services.git.login_info'.tr();
|
||||
case ServiceTypes.vpn:
|
||||
case ServiceTypes.ocserv:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String get subdomain {
|
||||
switch (this) {
|
||||
case ServiceTypes.passwordManager:
|
||||
case ServiceTypes.bitwarden:
|
||||
return 'password';
|
||||
case ServiceTypes.video:
|
||||
case ServiceTypes.jitsi:
|
||||
return 'meet';
|
||||
case ServiceTypes.cloud:
|
||||
case ServiceTypes.nextcloud:
|
||||
return 'cloud';
|
||||
case ServiceTypes.socialNetwork:
|
||||
case ServiceTypes.pleroma:
|
||||
return 'social';
|
||||
case ServiceTypes.git:
|
||||
case ServiceTypes.gitea:
|
||||
return 'git';
|
||||
case ServiceTypes.vpn:
|
||||
case ServiceTypes.messenger:
|
||||
case ServiceTypes.ocserv:
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -118,21 +110,19 @@ extension ServiceTypesExt on ServiceTypes {
|
|||
|
||||
IconData get icon {
|
||||
switch (this) {
|
||||
case ServiceTypes.mail:
|
||||
case ServiceTypes.mailserver:
|
||||
return BrandIcons.envelope;
|
||||
case ServiceTypes.messenger:
|
||||
return BrandIcons.messanger;
|
||||
case ServiceTypes.passwordManager:
|
||||
case ServiceTypes.bitwarden:
|
||||
return BrandIcons.key;
|
||||
case ServiceTypes.video:
|
||||
case ServiceTypes.jitsi:
|
||||
return BrandIcons.webcam;
|
||||
case ServiceTypes.cloud:
|
||||
case ServiceTypes.nextcloud:
|
||||
return BrandIcons.upload;
|
||||
case ServiceTypes.socialNetwork:
|
||||
case ServiceTypes.pleroma:
|
||||
return BrandIcons.social;
|
||||
case ServiceTypes.git:
|
||||
case ServiceTypes.gitea:
|
||||
return BrandIcons.git;
|
||||
case ServiceTypes.vpn:
|
||||
case ServiceTypes.ocserv:
|
||||
return Icons.vpn_lock_outlined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
}
|
||||
if (job is ServiceToggleJob) {
|
||||
hasServiceJobs = true;
|
||||
await api.switchService(job.type, job.needToTurnOn);
|
||||
await api.switchService(job.type.name, job.needToTurnOn);
|
||||
}
|
||||
if (job is CreateSSHKeyJob) {
|
||||
await usersCubit.addSshKey(job.user, job.publicKey);
|
||||
|
|
|
@ -1,31 +1,48 @@
|
|||
import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
|
||||
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';
|
||||
|
||||
part 'services_state.dart';
|
||||
|
||||
class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
|
||||
ServicesCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(serverInstallationCubit, ServicesState.allOff());
|
||||
: super(serverInstallationCubit, const ServicesState.empty());
|
||||
final ServerApi api = ServerApi();
|
||||
Timer? timer;
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final Map<ServiceTypes, bool> statuses = await api.servicesPowerCheck();
|
||||
final List<Service> services = await api.getAllServices();
|
||||
emit(
|
||||
ServicesState(
|
||||
isPasswordManagerEnable: statuses[ServiceTypes.passwordManager]!,
|
||||
isCloudEnable: statuses[ServiceTypes.cloud]!,
|
||||
isGitEnable: statuses[ServiceTypes.git]!,
|
||||
isSocialNetworkEnable: statuses[ServiceTypes.socialNetwork]!,
|
||||
isVpnEnable: statuses[ServiceTypes.vpn]!,
|
||||
services: services,
|
||||
),
|
||||
);
|
||||
timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reload({final bool useTimer = false}) async {
|
||||
final List<Service> services = await api.getAllServices();
|
||||
emit(
|
||||
ServicesState(
|
||||
services: services,
|
||||
),
|
||||
);
|
||||
if (useTimer) {
|
||||
timer = Timer(const Duration(seconds: 60), () => reload(useTimer: true));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() async {
|
||||
emit(ServicesState.allOff());
|
||||
emit(const ServicesState.empty());
|
||||
if (timer != null && timer!.isActive) {
|
||||
timer!.cancel();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,90 +1,43 @@
|
|||
part of 'services_cubit.dart';
|
||||
|
||||
class ServicesState extends ServerInstallationDependendState {
|
||||
factory ServicesState.allOn() => const ServicesState(
|
||||
isPasswordManagerEnable: true,
|
||||
isCloudEnable: true,
|
||||
isGitEnable: true,
|
||||
isSocialNetworkEnable: true,
|
||||
isVpnEnable: true,
|
||||
);
|
||||
|
||||
factory ServicesState.allOff() => const ServicesState(
|
||||
isPasswordManagerEnable: false,
|
||||
isCloudEnable: false,
|
||||
isGitEnable: false,
|
||||
isSocialNetworkEnable: false,
|
||||
isVpnEnable: false,
|
||||
);
|
||||
const ServicesState({
|
||||
required this.isPasswordManagerEnable,
|
||||
required this.isCloudEnable,
|
||||
required this.isGitEnable,
|
||||
required this.isSocialNetworkEnable,
|
||||
required this.isVpnEnable,
|
||||
required this.services,
|
||||
});
|
||||
|
||||
final bool isPasswordManagerEnable;
|
||||
final bool isCloudEnable;
|
||||
final bool isGitEnable;
|
||||
final bool isSocialNetworkEnable;
|
||||
final bool isVpnEnable;
|
||||
const ServicesState.empty() : this(services: const []);
|
||||
|
||||
ServicesState enableList(
|
||||
final List<ServiceTypes> list,
|
||||
) =>
|
||||
ServicesState(
|
||||
isPasswordManagerEnable: list.contains(ServiceTypes.passwordManager)
|
||||
? true
|
||||
: isPasswordManagerEnable,
|
||||
isCloudEnable: list.contains(ServiceTypes.cloud) ? true : isCloudEnable,
|
||||
isGitEnable:
|
||||
list.contains(ServiceTypes.git) ? true : isPasswordManagerEnable,
|
||||
isSocialNetworkEnable: list.contains(ServiceTypes.socialNetwork)
|
||||
? true
|
||||
: isPasswordManagerEnable,
|
||||
isVpnEnable:
|
||||
list.contains(ServiceTypes.vpn) ? true : isPasswordManagerEnable,
|
||||
);
|
||||
final List<Service> services;
|
||||
bool get isPasswordManagerEnable => services.firstWhere((final service) => service.id == 'bitwarden', orElse: () => Service.empty).isEnabled;
|
||||
bool get isCloudEnable => services.firstWhere((final service) => service.id == 'nextcloud', orElse: () => Service.empty).isEnabled;
|
||||
bool get isGitEnable => services.firstWhere((final service) => service.id == 'gitea', orElse: () => Service.empty).isEnabled;
|
||||
bool get isSocialNetworkEnable => services.firstWhere((final service) => service.id == 'pleroma', orElse: () => Service.empty).isEnabled;
|
||||
bool get isVpnEnable => services.firstWhere((final service) => service.id == 'ocserv', orElse: () => Service.empty).isEnabled;
|
||||
|
||||
ServicesState disableList(
|
||||
final List<ServiceTypes> list,
|
||||
) =>
|
||||
ServicesState(
|
||||
isPasswordManagerEnable: list.contains(ServiceTypes.passwordManager)
|
||||
? false
|
||||
: isPasswordManagerEnable,
|
||||
isCloudEnable:
|
||||
list.contains(ServiceTypes.cloud) ? false : isCloudEnable,
|
||||
isGitEnable:
|
||||
list.contains(ServiceTypes.git) ? false : isPasswordManagerEnable,
|
||||
isSocialNetworkEnable: list.contains(ServiceTypes.socialNetwork)
|
||||
? false
|
||||
: isPasswordManagerEnable,
|
||||
isVpnEnable:
|
||||
list.contains(ServiceTypes.vpn) ? false : isPasswordManagerEnable,
|
||||
);
|
||||
Service? getServiceById(final String id) {
|
||||
final service = services.firstWhere((final service) => service.id == id, orElse: () => Service.empty);
|
||||
if (service.id == 'empty') {
|
||||
return null;
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
isPasswordManagerEnable,
|
||||
isCloudEnable,
|
||||
isGitEnable,
|
||||
isSocialNetworkEnable,
|
||||
isVpnEnable
|
||||
services,
|
||||
];
|
||||
|
||||
bool isEnableByType(final ServiceTypes type) {
|
||||
switch (type) {
|
||||
case ServiceTypes.passwordManager:
|
||||
case ServiceTypes.bitwarden:
|
||||
return isPasswordManagerEnable;
|
||||
case ServiceTypes.cloud:
|
||||
case ServiceTypes.nextcloud:
|
||||
return isCloudEnable;
|
||||
case ServiceTypes.socialNetwork:
|
||||
case ServiceTypes.pleroma:
|
||||
return isSocialNetworkEnable;
|
||||
case ServiceTypes.git:
|
||||
case ServiceTypes.gitea:
|
||||
return isGitEnable;
|
||||
case ServiceTypes.vpn:
|
||||
case ServiceTypes.ocserv:
|
||||
return isVpnEnable;
|
||||
default:
|
||||
throw Exception('wrong state');
|
||||
|
|
|
@ -48,7 +48,7 @@ class ToggleJob extends ClientJob {
|
|||
required final super.title,
|
||||
});
|
||||
|
||||
final dynamic type;
|
||||
final ServiceTypes type;
|
||||
|
||||
@override
|
||||
List<Object> get props => [...super.props, type];
|
||||
|
@ -56,7 +56,7 @@ class ToggleJob extends ClientJob {
|
|||
|
||||
class ServiceToggleJob extends ToggleJob {
|
||||
ServiceToggleJob({
|
||||
required final ServiceTypes super.type,
|
||||
required final super.type,
|
||||
required this.needToTurnOn,
|
||||
}) : super(
|
||||
title:
|
||||
|
|
|
@ -12,6 +12,7 @@ class Service {
|
|||
required this.description,
|
||||
required this.isEnabled,
|
||||
required this.isRequired,
|
||||
required this.isMovable,
|
||||
required this.status,
|
||||
required this.storageUsage,
|
||||
required this.svgIcon,
|
||||
|
@ -26,6 +27,7 @@ class Service {
|
|||
description: service.description,
|
||||
isEnabled: service.isEnabled,
|
||||
isRequired: service.isRequired,
|
||||
isMovable: service.isMovable,
|
||||
status: ServiceStatus.fromGraphQL(service.status),
|
||||
storageUsage: ServiceStorageUsage(
|
||||
used: DiskSize(byte: int.parse(service.storageUsage.usedSpace)),
|
||||
|
@ -40,11 +42,29 @@ class Service {
|
|||
url: service.url,
|
||||
);
|
||||
|
||||
static Service empty = Service(
|
||||
id: 'empty',
|
||||
displayName: '',
|
||||
description: '',
|
||||
isEnabled: false,
|
||||
isRequired: false,
|
||||
isMovable: false,
|
||||
status: ServiceStatus.off,
|
||||
storageUsage: ServiceStorageUsage(
|
||||
used: DiskSize(byte: 0),
|
||||
volume: '',
|
||||
),
|
||||
svgIcon: '',
|
||||
dnsRecords: [],
|
||||
url: '',
|
||||
);
|
||||
|
||||
final String id;
|
||||
final String displayName;
|
||||
final String description;
|
||||
final bool isEnabled;
|
||||
final bool isRequired;
|
||||
final bool isMovable;
|
||||
final ServiceStatus status;
|
||||
final ServiceStorageUsage storageUsage;
|
||||
final String svgIcon;
|
||||
|
|
|
@ -25,9 +25,11 @@ class BrandCards {
|
|||
static Widget filled({
|
||||
required final Widget child,
|
||||
final bool tertiary = false,
|
||||
final bool error = false,
|
||||
}) =>
|
||||
_FilledCard(
|
||||
tertiary: tertiary,
|
||||
error: error,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
@ -81,10 +83,12 @@ class _FilledCard extends StatelessWidget {
|
|||
const _FilledCard({
|
||||
required this.child,
|
||||
required this.tertiary,
|
||||
required this.error,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final bool tertiary;
|
||||
final bool error;
|
||||
@override
|
||||
Widget build(final BuildContext context) => Card(
|
||||
elevation: 0.0,
|
||||
|
@ -92,7 +96,8 @@ class _FilledCard extends StatelessWidget {
|
|||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
color: tertiary
|
||||
color: error ? Theme.of(context).colorScheme.errorContainer
|
||||
: tertiary
|
||||
? Theme.of(context).colorScheme.tertiaryContainer
|
||||
: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: child,
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ServicePage extends StatefulWidget {
|
||||
const ServicePage({final super.key});
|
||||
const ServicePage({required this.serviceId, final super.key});
|
||||
|
||||
final String serviceId;
|
||||
|
||||
@override
|
||||
State<ServicePage> createState() => _ServicePageState();
|
||||
|
@ -11,73 +18,190 @@ class ServicePage extends StatefulWidget {
|
|||
|
||||
class _ServicePageState extends State<ServicePage> {
|
||||
@override
|
||||
Widget build(final BuildContext context) => BrandHeroScreen(
|
||||
Widget build(final BuildContext context) {
|
||||
final Service? service =
|
||||
context.watch<ServicesCubit>().state.getServiceById(widget.serviceId);
|
||||
|
||||
if (service == null) {
|
||||
return const BrandHeroScreen(
|
||||
hasBackButton: true,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(
|
||||
Icons.question_mark_outlined,
|
||||
size: 48,
|
||||
),
|
||||
Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'My Incredible Service',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandCards.outlined(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 24),
|
||||
child: const Icon(
|
||||
Icons.check_box_outlined,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 130),
|
||||
child: const Text(''),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: null,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 24),
|
||||
child: const Icon(
|
||||
Icons.language_outlined,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 130),
|
||||
child: const Text('Your Cool Domain'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
);
|
||||
}
|
||||
return BrandHeroScreen(
|
||||
hasBackButton: true,
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: SvgPicture.string(
|
||||
service.svgIcon,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
service.displayName,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ServiceStatusCard(status: service.status),
|
||||
const SizedBox(height: 16),
|
||||
if (service.url != null)
|
||||
ListTile(
|
||||
iconColor: Theme.of(context).colorScheme.onBackground,
|
||||
onTap: () => _launchURL(service.url),
|
||||
leading: const Icon(Icons.open_in_browser),
|
||||
title: Text(
|
||||
'Open in browser',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
subtitle: Text(
|
||||
service.url!.replaceAll('https://', ''),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
ListTile(
|
||||
iconColor: Theme.of(context).colorScheme.onBackground,
|
||||
onTap: () => {},
|
||||
leading: const Icon(Icons.restart_alt_outlined),
|
||||
title: Text(
|
||||
'Restart service',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
iconColor: Theme.of(context).colorScheme.onBackground,
|
||||
onTap: () => {},
|
||||
leading: const Icon(Icons.power_settings_new),
|
||||
title: Text(
|
||||
'Disable service',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
if (service.isMovable)
|
||||
ListTile(
|
||||
iconColor: Theme.of(context).colorScheme.onBackground,
|
||||
onTap: () => {},
|
||||
leading: const Icon(Icons.drive_file_move_outlined),
|
||||
title: Text(
|
||||
'Move to another volume',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceStatusCard extends StatelessWidget {
|
||||
const ServiceStatusCard({
|
||||
required this.status,
|
||||
final super.key,
|
||||
});
|
||||
final ServiceStatus status;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
switch (status) {
|
||||
case ServiceStatus.active:
|
||||
return BrandCards.filled(
|
||||
child: const ListTile(
|
||||
leading: Icon(
|
||||
Icons.check_circle_outline,
|
||||
size: 24,
|
||||
),
|
||||
title: Text('Up and running'),
|
||||
),
|
||||
);
|
||||
case ServiceStatus.inactive:
|
||||
return BrandCards.filled(
|
||||
child: const ListTile(
|
||||
leading: Icon(
|
||||
Icons.stop_circle_outlined,
|
||||
size: 24,
|
||||
),
|
||||
title: Text('Stopped'),
|
||||
),
|
||||
tertiary: true,
|
||||
);
|
||||
case ServiceStatus.failed:
|
||||
return BrandCards.filled(
|
||||
child: const ListTile(
|
||||
leading: Icon(
|
||||
Icons.error_outline,
|
||||
size: 24,
|
||||
),
|
||||
title: Text('Failed to start'),
|
||||
),
|
||||
error: true,
|
||||
);
|
||||
case ServiceStatus.off:
|
||||
return BrandCards.filled(
|
||||
child: const ListTile(
|
||||
leading: Icon(
|
||||
Icons.power_settings_new,
|
||||
size: 24,
|
||||
),
|
||||
title: Text('Disabled'),
|
||||
),
|
||||
tertiary: true,
|
||||
);
|
||||
case ServiceStatus.activating:
|
||||
return BrandCards.filled(
|
||||
child: const ListTile(
|
||||
leading: Icon(
|
||||
Icons.restart_alt_outlined,
|
||||
size: 24,
|
||||
),
|
||||
title: Text('Activating'),
|
||||
),
|
||||
tertiary: true,
|
||||
);
|
||||
case ServiceStatus.deactivating:
|
||||
return BrandCards.filled(
|
||||
child: const ListTile(
|
||||
leading: Icon(
|
||||
Icons.restart_alt_outlined,
|
||||
size: 24,
|
||||
),
|
||||
title: Text('Deactivating'),
|
||||
),
|
||||
tertiary: true,
|
||||
);
|
||||
case ServiceStatus.reloading:
|
||||
return BrandCards.filled(
|
||||
child: const ListTile(
|
||||
leading: Icon(
|
||||
Icons.restart_alt_outlined,
|
||||
size: 24,
|
||||
),
|
||||
title: Text('Restarting'),
|
||||
),
|
||||
tertiary: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _launchURL(final url) async {
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/config/text_themes.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/jobs/jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/job.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_switch/brand_switch.dart';
|
||||
|
@ -18,17 +15,17 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/ui/pages/services/service_page.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/utils/ui_helpers.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
const switchableServices = [
|
||||
ServiceTypes.passwordManager,
|
||||
ServiceTypes.cloud,
|
||||
ServiceTypes.socialNetwork,
|
||||
ServiceTypes.git,
|
||||
ServiceTypes.vpn,
|
||||
ServiceTypes.bitwarden,
|
||||
ServiceTypes.nextcloud,
|
||||
ServiceTypes.pleroma,
|
||||
ServiceTypes.gitea,
|
||||
ServiceTypes.ocserv,
|
||||
];
|
||||
|
||||
class ServicesPage extends StatefulWidget {
|
||||
|
@ -39,18 +36,14 @@ class ServicesPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
void _launchURL(final url) async {
|
||||
final canLaunch = await canLaunchUrlString(url);
|
||||
|
||||
if (canLaunch) {
|
||||
try {
|
||||
await launchUrlString(
|
||||
url,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
} else {
|
||||
throw 'Could not launch $url';
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,23 +60,28 @@ class _ServicesPageState extends State<ServicesPage> {
|
|||
title: 'basis.services'.tr(),
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
BrandText.body1('services.title'.tr()),
|
||||
const SizedBox(height: 24),
|
||||
if (!isReady) ...[const NotReadyCard(), const SizedBox(height: 24)],
|
||||
...ServiceTypes.values
|
||||
.map(
|
||||
(final t) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 30,
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<ServicesCubit>().reload();
|
||||
},
|
||||
child: ListView(
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
BrandText.body1('services.title'.tr()),
|
||||
const SizedBox(height: 24),
|
||||
if (!isReady) ...[const NotReadyCard(), const SizedBox(height: 24)],
|
||||
...ServiceTypes.values
|
||||
.map(
|
||||
(final t) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 30,
|
||||
),
|
||||
child: _Card(serviceType: t),
|
||||
),
|
||||
child: _Card(serviceType: t),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
],
|
||||
)
|
||||
.toList()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -97,7 +95,6 @@ class _Card extends StatelessWidget {
|
|||
Widget build(final BuildContext context) {
|
||||
final isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
final changeTab = context.read<ChangeTab>().onPress;
|
||||
|
||||
final serviceState = context.watch<ServicesCubit>().state;
|
||||
final jobsCubit = context.watch<JobsCubit>();
|
||||
|
@ -118,21 +115,11 @@ class _Card extends StatelessWidget {
|
|||
final domainName = UiHelpers.getDomainName(config);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: isSwitchOn
|
||||
? () => showDialog<void>(
|
||||
context: context,
|
||||
// isScrollControlled: true,
|
||||
// backgroundColor: Colors.transparent,
|
||||
builder: (final BuildContext context) => _ServiceDetails(
|
||||
serviceType: serviceType,
|
||||
status:
|
||||
isSwitchOn ? StateType.stable : StateType.uninitialized,
|
||||
title: serviceType.title,
|
||||
icon: serviceType.icon,
|
||||
changeTab: changeTab,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
onTap: () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
ServicePage(serviceId: serviceType.name)
|
||||
)
|
||||
),
|
||||
child: BrandCards.big(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -202,7 +189,7 @@ class _Card extends StatelessWidget {
|
|||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
if (serviceType == ServiceTypes.mail)
|
||||
if (serviceType == ServiceTypes.mailserver)
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
|
@ -246,257 +233,3 @@ class _Card extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ServiceDetails extends StatelessWidget {
|
||||
const _ServiceDetails({
|
||||
required this.serviceType,
|
||||
required this.icon,
|
||||
required this.status,
|
||||
required this.title,
|
||||
required this.changeTab,
|
||||
});
|
||||
|
||||
final ServiceTypes serviceType;
|
||||
final IconData icon;
|
||||
final StateType status;
|
||||
final String title;
|
||||
final ValueChanged<int> changeTab;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
late Widget child;
|
||||
|
||||
final config = context.watch<ServerInstallationCubit>().state;
|
||||
final domainName = UiHelpers.getDomainName(config);
|
||||
|
||||
final linksStyle = body1Style.copyWith(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: BrandColors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.underline,
|
||||
);
|
||||
|
||||
final textStyle = body1Style.copyWith(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: BrandColors.black,
|
||||
);
|
||||
switch (serviceType) {
|
||||
case ServiceTypes.mail:
|
||||
child = RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'services.mail.bottom_sheet.1'.tr(args: [domainName]),
|
||||
style: textStyle,
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 5)),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0.8),
|
||||
child: GestureDetector(
|
||||
child: Text(
|
||||
'services.mail.bottom_sheet.2'.tr(),
|
||||
style: linksStyle,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
changeTab(2);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServiceTypes.messenger:
|
||||
child = RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text:
|
||||
'services.messenger.bottom_sheet.1'.tr(args: [domainName]),
|
||||
style: textStyle,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServiceTypes.passwordManager:
|
||||
child = RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'services.password_manager.bottom_sheet.1'
|
||||
.tr(args: [domainName]),
|
||||
style: textStyle,
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 5)),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0.8),
|
||||
child: GestureDetector(
|
||||
onTap: () => _launchURL('https://password.$domainName'),
|
||||
child: Text(
|
||||
'password.$domainName',
|
||||
style: linksStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServiceTypes.video:
|
||||
child = RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'services.video.bottom_sheet.1'.tr(args: [domainName]),
|
||||
style: textStyle,
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 5)),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0.8),
|
||||
child: GestureDetector(
|
||||
onTap: () => _launchURL('https://meet.$domainName'),
|
||||
child: Text(
|
||||
'meet.$domainName',
|
||||
style: linksStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServiceTypes.cloud:
|
||||
child = RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'services.cloud.bottom_sheet.1'.tr(args: [domainName]),
|
||||
style: textStyle,
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 5)),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0.8),
|
||||
child: GestureDetector(
|
||||
onTap: () => _launchURL('https://cloud.$domainName'),
|
||||
child: Text(
|
||||
'cloud.$domainName',
|
||||
style: linksStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServiceTypes.socialNetwork:
|
||||
child = RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'services.social_network.bottom_sheet.1'
|
||||
.tr(args: [domainName]),
|
||||
style: textStyle,
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 5)),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0.8),
|
||||
child: GestureDetector(
|
||||
onTap: () => _launchURL('https://social.$domainName'),
|
||||
child: Text(
|
||||
'social.$domainName',
|
||||
style: linksStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServiceTypes.git:
|
||||
child = RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'services.git.bottom_sheet.1'.tr(args: [domainName]),
|
||||
style: textStyle,
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 5)),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0.8),
|
||||
child: GestureDetector(
|
||||
onTap: () => _launchURL('https://git.$domainName'),
|
||||
child: Text(
|
||||
'git.$domainName',
|
||||
style: linksStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServiceTypes.vpn:
|
||||
child = Text(
|
||||
'services.vpn.bottom_sheet.1'.tr(),
|
||||
);
|
||||
}
|
||||
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
width: 350,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: paddingH15V30,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
IconStatusMask(
|
||||
status: status,
|
||||
child: Icon(icon, size: 40, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BrandText.h2(title),
|
||||
const SizedBox(height: 10),
|
||||
child,
|
||||
const SizedBox(height: 40),
|
||||
Center(
|
||||
child: Container(
|
||||
child: BrandButton.rised(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
text: 'basis.close'.tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue