refactor: estrai showUpdateDialog e parseTagName in file dedicati

Sposta la logica del dialog in lib/shared/widgets/update_dialog.dart
per renderla importabile nei test senza duplicare il codice.
Estrae parseTagName come metodo @visibleForTesting in UpdateService
isolando il parsing JSON dalla chiamata HTTP. app.dart semplificato
di conseguenza.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 16:28:13 +02:00
parent 9486a70f1f
commit 3da8ccc4d3
3 changed files with 45 additions and 33 deletions
+2 -31
View File
@@ -5,7 +5,7 @@ import 'features/converter/presentation/pages/converter_page.dart';
import 'features/shopping_list/presentation/pages/shopping_list_page.dart'; import 'features/shopping_list/presentation/pages/shopping_list_page.dart';
import 'features/guide/presentation/widgets/info_bottom_sheet.dart'; import 'features/guide/presentation/widgets/info_bottom_sheet.dart';
import 'shared/services/update_service.dart'; import 'shared/services/update_service.dart';
import 'shared/services/url_launcher_service.dart'; import 'shared/widgets/update_dialog.dart';
class BitePlanApp extends StatelessWidget { class BitePlanApp extends StatelessWidget {
const BitePlanApp({super.key}); const BitePlanApp({super.key});
@@ -33,8 +33,6 @@ class _MainScaffoldState extends State<_MainScaffold> {
late final List<Widget> _pages; late final List<Widget> _pages;
static const _titles = ['Piano Pasti', 'Convertitore', 'Lista della spesa']; static const _titles = ['Piano Pasti', 'Convertitore', 'Lista della spesa'];
static const _releasesUrl =
'https://github.com/davide3011/biteplan/releases/latest';
@override @override
void initState() { void initState() {
@@ -48,38 +46,11 @@ class _MainScaffoldState extends State<_MainScaffold> {
]; ];
UpdateService.checkUpdate().then((newVersion) { UpdateService.checkUpdate().then((newVersion) {
if (newVersion != null && mounted) { if (newVersion != null && mounted) {
_showUpdateDialog(newVersion); showUpdateDialog(context, newVersion);
} }
}); });
} }
void _showUpdateDialog(String newVersion) {
showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.system_update_outlined, size: 32),
title: const Text('Aggiornamento disponibile'),
content: Text(
'È disponibile la versione $newVersion di BitePlan.\n'
'Vuoi scaricare il nuovo APK?',
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Più tardi'),
),
FilledButton(
onPressed: () {
Navigator.of(ctx).pop();
UrlLauncherService.launch(_releasesUrl);
},
child: const Text('Scarica'),
),
],
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
+12 -2
View File
@@ -27,14 +27,24 @@ class UpdateService {
final body = await response.transform(utf8.decoder).join(); final body = await response.transform(utf8.decoder).join();
client.close(); client.close();
if (response.statusCode != 200) return null; if (response.statusCode != 200) return null;
final data = jsonDecode(body) as Map<String, dynamic>; final tag = parseTagName(body);
final tag = (data['tag_name'] as String).replaceFirst(RegExp(r'^v'), ''); if (tag == null) return null;
return isNewer(tag, kAppVersion) ? tag : null; return isNewer(tag, kAppVersion) ? tag : null;
} catch (_) { } catch (_) {
return null; return null;
} }
} }
@visibleForTesting
static String? parseTagName(String body) {
try {
final data = jsonDecode(body) as Map<String, dynamic>;
return (data['tag_name'] as String).replaceFirst(RegExp(r'^v'), '');
} catch (_) {
return null;
}
}
@visibleForTesting @visibleForTesting
static bool isNewer(String remote, String local) { static bool isNewer(String remote, String local) {
final r = parseVersion(remote); final r = parseVersion(remote);
+31
View File
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import '../services/url_launcher_service.dart';
const _releasesUrl = 'https://github.com/davide3011/biteplan/releases/latest';
void showUpdateDialog(BuildContext context, String newVersion) {
showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.system_update_outlined, size: 32),
title: const Text('Aggiornamento disponibile'),
content: Text(
'È disponibile la versione $newVersion di BitePlan.\n'
'Vuoi scaricare il nuovo APK?',
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Più tardi'),
),
FilledButton(
onPressed: () {
Navigator.of(ctx).pop();
UrlLauncherService.launch(_releasesUrl);
},
child: const Text('Scarica'),
),
],
),
);
}