refactor(ui): Refactor DNS screen and introduce skeletons

This commit is contained in:
Inex Code 2024-12-06 21:42:43 +03:00
parent c86c00cdf0
commit 65ec990410
No known key found for this signature in database
6 changed files with 213 additions and 189 deletions

View file

@ -0,0 +1,76 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
import 'package:selfprivacy/ui/atoms/cards/filled_card.dart';
class DnsStateCard extends StatelessWidget {
const DnsStateCard({
required this.dnsState,
required this.fixCallback,
super.key,
});
final DnsRecordsStatus dnsState;
final Function fixCallback;
@override
Widget build(final BuildContext context) {
String description = '';
String subtitle = '';
Icon icon = const Icon(
Icons.check_circle_outline,
size: 24.0,
);
bool isError = false;
switch (dnsState) {
case DnsRecordsStatus.uninitialized:
description = 'domain.uninitialized'.tr();
icon = const Icon(
Icons.refresh,
size: 24.0,
);
isError = false;
break;
case DnsRecordsStatus.refreshing:
description = 'domain.refreshing'.tr();
icon = const Icon(
Icons.refresh,
size: 24.0,
);
isError = false;
break;
case DnsRecordsStatus.good:
description = 'domain.ok'.tr();
icon = const Icon(
Icons.check_circle_outline,
size: 24.0,
);
isError = false;
break;
case DnsRecordsStatus.error:
description = 'domain.error'.tr();
subtitle = 'domain.error_subtitle'.tr();
icon = const Icon(
Icons.error_outline,
size: 24.0,
);
isError = true;
break;
}
return FilledCard(
error: isError,
child: ListTile(
onTap: dnsState == DnsRecordsStatus.error ? () => fixCallback() : null,
leading: icon,
title: Text(description),
subtitle: subtitle != '' ? Text(subtitle) : null,
textColor: isError
? Theme.of(context).colorScheme.onErrorContainer
: Theme.of(context).colorScheme.onSurfaceVariant,
iconColor: isError
? Theme.of(context).colorScheme.onErrorContainer
: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
}

View file

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
class DnsRecordItem extends StatelessWidget {
const DnsRecordItem({
required this.dnsRecord,
required this.refreshing,
super.key,
});
final DesiredDnsRecord dnsRecord;
final bool refreshing;
@override
Widget build(final BuildContext context) {
final Color goodColor = Theme.of(context).colorScheme.onSurface;
final Color errorColor = Theme.of(context).colorScheme.error;
final Color neutralColor = Theme.of(context).colorScheme.onSurface;
return ListTile(
leading: Icon(
dnsRecord.isSatisfied
? Icons.check_circle_outline
: refreshing
? Icons.refresh
: Icons.error_outline,
color: dnsRecord.isSatisfied
? goodColor
: refreshing
? neutralColor
: errorColor,
),
title: Text(dnsRecord.displayName ?? dnsRecord.name),
subtitle: Text(
dnsRecord.content,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
}
}

View file

@ -1,14 +1,19 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/ui/atoms/cards/filled_card.dart';
import 'package:selfprivacy/ui/atoms/icons/brand_icons.dart';
import 'package:selfprivacy/ui/atoms/list_tiles/section_headline.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
import 'package:selfprivacy/ui/molecules/cards/dns_state_card.dart';
import 'package:selfprivacy/ui/molecules/list_items/dns_record_item.dart';
import 'package:selfprivacy/utils/fake_data.dart';
import 'package:skeletonizer/skeletonizer.dart';
@RoutePage()
class DnsDetailsPage extends StatefulWidget {
@ -19,69 +24,6 @@ class DnsDetailsPage extends StatefulWidget {
}
class _DnsDetailsPageState extends State<DnsDetailsPage> {
Widget _getStateCard(
final DnsRecordsStatus dnsState,
final Function fixCallback,
) {
String description = '';
String subtitle = '';
Icon icon = const Icon(
Icons.check_circle_outline,
size: 24.0,
);
bool isError = false;
switch (dnsState) {
case DnsRecordsStatus.uninitialized:
description = 'domain.uninitialized'.tr();
icon = const Icon(
Icons.refresh,
size: 24.0,
);
isError = false;
break;
case DnsRecordsStatus.refreshing:
description = 'domain.refreshing'.tr();
icon = const Icon(
Icons.refresh,
size: 24.0,
);
isError = false;
break;
case DnsRecordsStatus.good:
description = 'domain.ok'.tr();
icon = const Icon(
Icons.check_circle_outline,
size: 24.0,
);
isError = false;
break;
case DnsRecordsStatus.error:
description = 'domain.error'.tr();
subtitle = 'domain.error_subtitle'.tr();
icon = const Icon(
Icons.error_outline,
size: 24.0,
);
isError = true;
break;
}
return FilledCard(
error: isError,
child: ListTile(
onTap: dnsState == DnsRecordsStatus.error ? () => fixCallback() : null,
leading: icon,
title: Text(description),
subtitle: subtitle != '' ? Text(subtitle) : null,
textColor: isError
? Theme.of(context).colorScheme.error
: Theme.of(context).colorScheme.onSurfaceVariant,
iconColor: isError
? Theme.of(context).colorScheme.error
: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
@override
Widget build(final BuildContext context) {
final bool isReady = context.watch<ServerInstallationCubit>().state
@ -89,6 +31,8 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
final String domain =
getIt<ResourcesModel>().serverDomain?.domainName ?? '';
final DnsRecordsState dnsCubit = context.watch<DnsRecordsCubit>().state;
final List<DesiredDnsRecord> dnsRecords =
context.watch<DnsRecordsCubit>().state.dnsRecords;
print(dnsCubit.dnsState);
@ -102,9 +46,35 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
);
}
final Color goodColor = Theme.of(context).colorScheme.onSurface;
final Color errorColor = Theme.of(context).colorScheme.error;
final Color neutralColor = Theme.of(context).colorScheme.onSurface;
final recordsToShow =
dnsRecords.isEmpty ? FakeSelfPrivacyData.desiredDnsRecords : dnsRecords;
final refreshing =
dnsCubit.dnsState == DnsRecordsStatus.refreshing || dnsRecords.isEmpty;
List<Widget> recordsSection(
final String title,
final String subtitle,
final DnsRecordsCategory category,
) =>
[
SectionHeadline(
title: title,
subtitle: subtitle,
),
...recordsToShow
.where(
(final dnsRecord) => dnsRecord.category == category,
)
.map(
(final dnsRecord) => Skeletonizer(
enabled: refreshing,
child: DnsRecordItem(
dnsRecord: dnsRecord,
refreshing: refreshing,
),
),
),
];
return BrandHeroScreen(
hasBackButton: true,
@ -112,132 +82,30 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
heroIcon: BrandIcons.globe,
heroTitle: 'domain.screen_title'.tr(),
children: <Widget>[
_getStateCard(
dnsCubit.dnsState,
() {
DnsStateCard(
dnsState: dnsCubit.dnsState,
fixCallback: () {
context.read<DnsRecordsCubit>().fix();
},
),
const SizedBox(height: 16.0),
ListTile(
title: Text(
'domain.services_title'.tr(),
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
subtitle: Text(
'domain.services_subtitle'.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
const Gap(8.0),
...recordsSection(
'domain.services_title'.tr(),
'domain.services_subtitle'.tr(),
DnsRecordsCategory.services,
),
...dnsCubit.dnsRecords
.where(
(final dnsRecord) =>
dnsRecord.category == DnsRecordsCategory.services,
)
.map(
(final dnsRecord) => Column(
children: [
ListTile(
leading: Icon(
dnsRecord.isSatisfied
? Icons.check_circle_outline
: dnsCubit.dnsState == DnsRecordsStatus.refreshing
? Icons.refresh
: Icons.error_outline,
color: dnsRecord.isSatisfied
? goodColor
: dnsCubit.dnsState == DnsRecordsStatus.refreshing
? neutralColor
: errorColor,
),
title: Text(dnsRecord.displayName ?? dnsRecord.name),
subtitle: Text(dnsRecord.content),
),
],
),
),
const SizedBox(height: 16.0),
ListTile(
title: Text(
'domain.email_title'.tr(),
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
subtitle: Text(
'domain.email_subtitle'.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
const Gap(8.0),
...recordsSection(
'domain.email_title'.tr(),
'domain.email_subtitle'.tr(),
DnsRecordsCategory.email,
),
...dnsCubit.dnsRecords
.where(
(final dnsRecord) =>
dnsRecord.category == DnsRecordsCategory.email,
)
.map(
(final dnsRecord) => Column(
children: [
ListTile(
leading: Icon(
dnsRecord.isSatisfied
? Icons.check_circle_outline
: dnsCubit.dnsState == DnsRecordsStatus.refreshing
? Icons.refresh
: Icons.error_outline,
color: dnsRecord.isSatisfied
? goodColor
: dnsCubit.dnsState == DnsRecordsStatus.refreshing
? neutralColor
: errorColor,
),
title: Text(dnsRecord.displayName ?? dnsRecord.name),
subtitle: Text(dnsRecord.name),
),
],
),
),
const SizedBox(height: 16.0),
ListTile(
title: Text(
'domain.other_title'.tr(),
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
subtitle: Text(
'domain.other_subtitle'.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
const Gap(8.0),
...recordsSection(
'domain.other_title'.tr(),
'domain.other_subtitle'.tr(),
DnsRecordsCategory.other,
),
...dnsCubit.dnsRecords
.where(
(final dnsRecord) =>
dnsRecord.category == DnsRecordsCategory.other,
)
.map(
(final dnsRecord) => Column(
children: [
ListTile(
leading: Icon(
dnsRecord.isSatisfied
? Icons.check_circle_outline
: dnsCubit.dnsState == DnsRecordsStatus.refreshing
? Icons.refresh
: Icons.error_outline,
color: dnsRecord.isSatisfied
? goodColor
: dnsCubit.dnsState == DnsRecordsStatus.refreshing
? neutralColor
: errorColor,
),
title: Text(dnsRecord.displayName ?? dnsRecord.name),
subtitle: Text(dnsRecord.content),
),
],
),
),
],
);
}

31
lib/utils/fake_data.dart Normal file
View file

@ -0,0 +1,31 @@
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
/// Fake data collections to fill skeletons
class FakeSelfPrivacyData {
static final List<DesiredDnsRecord> desiredDnsRecords = <DesiredDnsRecord>[
...List.generate(
6,
(final index) => const DesiredDnsRecord(
type: 'A',
name: 'example.com',
description: 'Service name',
content: '192.0.2.100',
),
),
...List.generate(
4,
(final index) => const DesiredDnsRecord(
type: 'TXT',
name: 'example.com',
description: 'Service name',
content: 'Some text text text',
),
),
const DesiredDnsRecord(
type: 'CAA',
name: 'example.com',
description: 'Service name',
content: 'Some very very long text text text',
),
];
}

View file

@ -1186,6 +1186,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
skeletonizer:
dependency: "direct main"
description:
name: skeletonizer
sha256: "3b202e4fa9c49b017d368fb0e570d4952bcd19972b67b2face071bdd68abbfae"
url: "https://pub.dev"
source: hosted
version: "1.4.2"
sky_engine:
dependency: transitive
description: flutter
@ -1403,10 +1411,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.2.4"
watcher:
dependency: transitive
description:

View file

@ -50,6 +50,7 @@ dependencies:
pretty_dio_logger: ^1.4.0
provider: ^6.1.2
pub_semver: ^2.1.4
skeletonizer: ^1.4.2
timezone: ^0.9.4
url_launcher: ^6.3.0
# wakelock: ^0.6.2 # TODO: Developer is not available, update later.