Rewrite services cubit and add basic service screen.

pull/111/head
Inex Code 2022-08-30 00:35:06 +04:00
parent 62929a4839
commit 7d8f8e1d38
11 changed files with 361 additions and 518 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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