New update server screen

This commit is contained in:
Juan Gilsanz Polo 2024-02-21 02:34:36 +01:00
parent e21e34668d
commit dcad63fe5c
4 changed files with 325 additions and 3 deletions

View file

@ -775,5 +775,6 @@
"noElementsReorderMessage": "Enable some elements on the show/hide tab to reorder them here.", "noElementsReorderMessage": "Enable some elements on the show/hide tab to reorder them here.",
"enablePlainDns": "Enable plain DNS", "enablePlainDns": "Enable plain DNS",
"enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol.", "enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol.",
"date": "Date" "date": "Date",
"loadingChangelog": "Loading changelog..."
} }

View file

@ -775,5 +775,6 @@
"noElementsReorderMessage": "Activa algunos elementos en la pestaña de mostrar/ocultar para reordenarlos aquí.", "noElementsReorderMessage": "Activa algunos elementos en la pestaña de mostrar/ocultar para reordenarlos aquí.",
"enablePlainDns": "Activar DNS simple (sin cifrado)", "enablePlainDns": "Activar DNS simple (sin cifrado)",
"enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado.", "enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado.",
"date": "Fecha" "date": "Fecha",
"loadingChangelog": "Cargando registro de cambios..."
} }

View file

@ -14,7 +14,7 @@ import 'package:adguard_home_manager/screens/settings/customization/customizatio
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp.dart';
import 'package:adguard_home_manager/screens/settings/statistics_settings/statistics_settings.dart'; import 'package:adguard_home_manager/screens/settings/statistics_settings/statistics_settings.dart';
import 'package:adguard_home_manager/screens/settings/safe_search_settings.dart'; import 'package:adguard_home_manager/screens/settings/safe_search_settings.dart';
import 'package:adguard_home_manager/screens/settings/update_server/update.dart'; import 'package:adguard_home_manager/screens/settings/update_server/update_screen.dart';
import 'package:adguard_home_manager/screens/settings/dns/dns.dart'; import 'package:adguard_home_manager/screens/settings/dns/dns.dart';
import 'package:adguard_home_manager/screens/settings/dns_rewrites/dns_rewrites.dart'; import 'package:adguard_home_manager/screens/settings/dns_rewrites/dns_rewrites.dart';
import 'package:adguard_home_manager/screens/servers/servers.dart'; import 'package:adguard_home_manager/screens/servers/servers.dart';

View file

@ -0,0 +1,320 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:html/parser.dart' as html;
import 'package:markdown/markdown.dart' as md;
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/functions/open_url.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
const _minExent = 64.0;
const _maxExent = 240.0;
const _iconMaxBottomPositionExent = 130.0;
const _iconMinBottomPositionExent = 34.0;
const _textMaxBottomPositionExent = 70.0;
const _textMinBottomPositionExent = 16.0;
const _versionTextMaxBottomPositionExent = 30.0;
const _versionTextMinBottomPositionExent = 0.0;
const _textMaxFontSize = 24.0;
const _textMinFontSize = 22.0;
const _iconSafetyMargin = 15.0;
const _iconSize = 45.0;
class UpdateScreen extends StatefulWidget {
const UpdateScreen({super.key});
@override
State<UpdateScreen> createState() => _UpdateScreenState();
}
class _UpdateScreenState extends State<UpdateScreen> {
final _scrollController = ScrollController();
bool _isScrolled = false;
String? _htmlChangelog;
void processChangelog() async {
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
final markdownResult = await compute(md.markdownToHtml, serversProvider.updateAvailable.data!.changelog!);
final htmlParsedResult = await compute(html.parse, markdownResult);
setState(() => _htmlChangelog = htmlParsedResult.outerHtml);
}
@override
void initState() {
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
_scrollController.addListener(() {
final newValue = _scrollController.offset > 20;
if (!(
serversProvider.updatingServer == false &&
serversProvider.updateAvailable.data!.canAutoupdate != null &&
serversProvider.updateAvailable.data!.canAutoupdate == true
)) return;
if (_isScrolled == newValue) return;
setState(() => _isScrolled = newValue);
});
processChangelog();
super.initState();
}
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
void update() async {
ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.requestingUpdate);
final result = await serversProvider.apiClient2!.requestUpdateServer();
processModal.close();
if (!mounted) return;
if (result.successful == true) {
serversProvider.recheckPeriodServerUpdated();
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.requestStartUpdateSuccessful,
color: Colors.green,
labelColor: Colors.white,
);
}
else {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.requestStartUpdateFailed,
color: Colors.red,
labelColor: Colors.white,
);
}
}
return Scaffold(
body: SafeArea(
top: false,
child: Stack(
children: [
CustomScrollView(
controller: _scrollController,
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: _Header(
onRefresh: () => serversProvider.checkServerUpdatesAvailable(
server: serversProvider.selectedServer!,
),
viewPaddingTop: MediaQuery.of(context).viewPadding.top
)
),
SliverList.list(
children: [
const SizedBox(height: 16),
if (_htmlChangelog != null) Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
"Changelog ${serversProvider.updateAvailable.data!.canAutoupdate == true
? serversProvider.updateAvailable.data!.newVersion
: serversProvider.updateAvailable.data!.currentVersion}",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
),
),
if (_htmlChangelog != null) Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Html(
data: _htmlChangelog,
onLinkTap: (url, context, attributes) => url != null ? openUrl(url) : null,
)
),
if (_htmlChangelog == null) Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 32),
child: Column(
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingChangelog,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
)
],
)
),
]
)
],
),
if (
serversProvider.updatingServer == false &&
serversProvider.updateAvailable.data!.canAutoupdate != null &&
serversProvider.updateAvailable.data!.canAutoupdate == true
) AnimatedPositioned(
right: 20,
bottom: _isScrolled ? -70 : 20,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
child: FloatingActionButton(
onPressed: () => update(),
tooltip: AppLocalizations.of(context)!.updateNow,
child: const Icon(Icons.download_rounded),
),
)
],
),
),
);
}
}
class _Header extends SliverPersistentHeaderDelegate {
final void Function() onRefresh;
final double viewPaddingTop;
const _Header({
required this.onRefresh,
required this.viewPaddingTop,
});
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
final serversProvider = Provider.of<ServersProvider>(context);
final iconMaxBottomPositionExent = _iconMaxBottomPositionExent + viewPaddingTop;
final iconMinBottomPositionExent = _iconMinBottomPositionExent + viewPaddingTop;
final textMaxBottomPositionExent = _textMaxBottomPositionExent + viewPaddingTop;
final textMinBottomPositionExent = _textMinBottomPositionExent + viewPaddingTop;
final versionTextMaxBottomPositionExent = _versionTextMaxBottomPositionExent + viewPaddingTop;
final versionTextMinBottomPositionExent = _versionTextMinBottomPositionExent + viewPaddingTop;
final iconPercentage = shrinkOffset.clamp(0, _maxExent-_minExent-_iconSafetyMargin)/(_maxExent-_minExent-_iconSafetyMargin);
final textPercentage = shrinkOffset.clamp(0, _maxExent-_minExent)/(_maxExent-_minExent);
final textFontSize = _textMinFontSize + (_textMaxFontSize-_textMinFontSize)*(1-textPercentage);
final mainText = _textMinBottomPositionExent + (textMaxBottomPositionExent-textMinBottomPositionExent)*(1-textPercentage);
final versionText = _versionTextMinBottomPositionExent + (versionTextMaxBottomPositionExent-versionTextMinBottomPositionExent)*(1-textPercentage);
final iconBottom = _iconMinBottomPositionExent + (iconMaxBottomPositionExent-iconMinBottomPositionExent)*(1-iconPercentage);
return LayoutBuilder(
builder: (context, constraints) => Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
),
child: Align(
alignment: Alignment.topLeft,
child: SafeArea(
bottom: false,
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
children: [
if (Navigator.of(context).canPop()) Positioned(
top: 8,
left: 0,
child: BackButton(
onPressed: () => Navigator.pop(context),
),
),
Positioned(
top: 8,
right: 0,
child: IconButton(
onPressed: onRefresh,
icon: const Icon(Icons.refresh_rounded),
tooltip: AppLocalizations.of(context)!.refresh,
)
),
Positioned(
bottom: iconBottom,
left: (constraints.maxWidth/2)-(_iconSize/2),
child: Opacity(
opacity: 1-iconPercentage,
child: Icon(
serversProvider.updateAvailable.data!.canAutoupdate == true
? Icons.system_update_rounded
: Icons.system_security_update_good_rounded,
size: _iconSize,
color: Theme.of(context).colorScheme.primary,
),
),
),
Positioned(
bottom: mainText,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.maxWidth-100
),
child: Text(
serversProvider.updateAvailable.loadStatus == LoadStatus.loading
? AppLocalizations.of(context)!.checkingUpdates
: serversProvider.updateAvailable.data!.canAutoupdate == true
? AppLocalizations.of(context)!.updateAvailable
: AppLocalizations.of(context)!.serverUpdated,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: textFontSize,
fontWeight: FontWeight.w400
),
),
)
),
Positioned(
bottom: versionText,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.maxWidth-100
),
child: Opacity(
opacity: 1-iconPercentage,
child: Text(
serversProvider.updateAvailable.data!.canAutoupdate == true
? "${AppLocalizations.of(context)!.newVersion}: ${serversProvider.updateAvailable.data!.newVersion ?? 'N/A'}"
: "${AppLocalizations.of(context)!.installedVersion}: ${serversProvider.updateAvailable.data!.currentVersion}",
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
),
),
)
)
],
),
),
),
),
);
}
@override
double get maxExtent => _maxExent + viewPaddingTop;
@override
double get minExtent => _minExent + viewPaddingTop;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => false;
}