mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-06 16:14:15 +00:00
refactor(ui): Refactor DNS screen and introduce skeletons
This commit is contained in:
parent
c86c00cdf0
commit
65ec990410
76
lib/ui/molecules/cards/dns_state_card.dart
Normal file
76
lib/ui/molecules/cards/dns_state_card.dart
Normal 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
lib/ui/molecules/list_items/dns_record_item.dart
Normal file
40
lib/ui/molecules/list_items/dns_record_item.dart
Normal 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,19 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.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/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/dns_records/dns_records_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_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/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/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/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()
|
@RoutePage()
|
||||||
class DnsDetailsPage extends StatefulWidget {
|
class DnsDetailsPage extends StatefulWidget {
|
||||||
|
@ -19,69 +24,6 @@ class DnsDetailsPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
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
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||||
|
@ -89,6 +31,8 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
||||||
final String domain =
|
final String domain =
|
||||||
getIt<ResourcesModel>().serverDomain?.domainName ?? '';
|
getIt<ResourcesModel>().serverDomain?.domainName ?? '';
|
||||||
final DnsRecordsState dnsCubit = context.watch<DnsRecordsCubit>().state;
|
final DnsRecordsState dnsCubit = context.watch<DnsRecordsCubit>().state;
|
||||||
|
final List<DesiredDnsRecord> dnsRecords =
|
||||||
|
context.watch<DnsRecordsCubit>().state.dnsRecords;
|
||||||
|
|
||||||
print(dnsCubit.dnsState);
|
print(dnsCubit.dnsState);
|
||||||
|
|
||||||
|
@ -102,9 +46,35 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Color goodColor = Theme.of(context).colorScheme.onSurface;
|
final recordsToShow =
|
||||||
final Color errorColor = Theme.of(context).colorScheme.error;
|
dnsRecords.isEmpty ? FakeSelfPrivacyData.desiredDnsRecords : dnsRecords;
|
||||||
final Color neutralColor = Theme.of(context).colorScheme.onSurface;
|
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(
|
return BrandHeroScreen(
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
|
@ -112,132 +82,30 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
||||||
heroIcon: BrandIcons.globe,
|
heroIcon: BrandIcons.globe,
|
||||||
heroTitle: 'domain.screen_title'.tr(),
|
heroTitle: 'domain.screen_title'.tr(),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_getStateCard(
|
DnsStateCard(
|
||||||
dnsCubit.dnsState,
|
dnsState: dnsCubit.dnsState,
|
||||||
() {
|
fixCallback: () {
|
||||||
context.read<DnsRecordsCubit>().fix();
|
context.read<DnsRecordsCubit>().fix();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
const Gap(8.0),
|
||||||
ListTile(
|
...recordsSection(
|
||||||
title: Text(
|
'domain.services_title'.tr(),
|
||||||
'domain.services_title'.tr(),
|
'domain.services_subtitle'.tr(),
|
||||||
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
|
DnsRecordsCategory.services,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
'domain.services_subtitle'.tr(),
|
|
||||||
style: Theme.of(context).textTheme.labelMedium,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
...dnsCubit.dnsRecords
|
const Gap(8.0),
|
||||||
.where(
|
...recordsSection(
|
||||||
(final dnsRecord) =>
|
'domain.email_title'.tr(),
|
||||||
dnsRecord.category == DnsRecordsCategory.services,
|
'domain.email_subtitle'.tr(),
|
||||||
)
|
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.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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
...dnsCubit.dnsRecords
|
const Gap(8.0),
|
||||||
.where(
|
...recordsSection(
|
||||||
(final dnsRecord) =>
|
'domain.other_title'.tr(),
|
||||||
dnsRecord.category == DnsRecordsCategory.email,
|
'domain.other_subtitle'.tr(),
|
||||||
)
|
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.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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
...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
31
lib/utils/fake_data.dart
Normal 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',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
12
pubspec.lock
12
pubspec.lock
|
@ -1186,6 +1186,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -1403,10 +1411,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.5"
|
version: "14.2.4"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -50,6 +50,7 @@ dependencies:
|
||||||
pretty_dio_logger: ^1.4.0
|
pretty_dio_logger: ^1.4.0
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
pub_semver: ^2.1.4
|
pub_semver: ^2.1.4
|
||||||
|
skeletonizer: ^1.4.2
|
||||||
timezone: ^0.9.4
|
timezone: ^0.9.4
|
||||||
url_launcher: ^6.3.0
|
url_launcher: ^6.3.0
|
||||||
# wakelock: ^0.6.2 # TODO: Developer is not available, update later.
|
# wakelock: ^0.6.2 # TODO: Developer is not available, update later.
|
||||||
|
|
Loading…
Reference in a new issue