Skip to content

Commit 405ec9e

Browse files
committed
refactor(update): delegate update flow to OrionUpdateProvider and remove local updater
- Add launchUpdateDialog confirmation UI that calls Provider.of<OrionUpdateProvider>.performUpdate(...) - Use configurable repo (overrideRepo) for GitHub API URLs instead of hardcoded owner - Wire update buttons to the new dialog, adjust button tints and update related UI text/TODO
1 parent 8947f31 commit 405ec9e

File tree

1 file changed

+78
-201
lines changed

1 file changed

+78
-201
lines changed

lib/settings/update_screen.dart

Lines changed: 78 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
// ignore_for_file: use_build_context_synchronously
1919

2020
import 'dart:convert';
21-
import 'dart:io';
2221
import 'dart:math';
2322

2423
import 'package:auto_size_text/auto_size_text.dart';
2524
import 'package:flutter/material.dart';
2625
import 'package:http/http.dart' as http;
2726
import 'package:logging/logging.dart';
2827
import 'package:phosphor_flutter/phosphor_flutter.dart';
28+
import 'package:provider/provider.dart';
29+
import 'package:orion/util/providers/orion_update_provider.dart';
2930

3031
import 'package:orion/glasser/glasser.dart';
3132
import 'package:orion/pubspec.dart';
@@ -55,9 +56,11 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
5556
String _currentVersion = '';
5657
String _release = 'BRANCH_dev';
5758
String _assetUrl = '';
59+
String _repo = 'Open-Resin-Alliance';
5860

5961
final Logger _logger = Logger('UpdateScreen');
6062
final OrionConfig _config = OrionConfig();
63+
// Update dialog state is handled by OrionUpdateProvider
6164

6265
@override
6366
void initState() {
@@ -68,6 +71,7 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
6871
_betaUpdatesOverride =
6972
_config.getFlag('releaseOverride', category: 'developer');
7073
_release = _config.getString('overrideRelease', category: 'developer');
74+
_repo = _config.getString('overrideRepo', category: 'developer');
7175
_logger.info('Firmware spoofing enabled: $_isFirmwareSpoofingEnabled');
7276
_logger.info('Beta updates override enabled: $_betaUpdatesOverride');
7377
_logger.info('Release channel override: $_release');
@@ -93,8 +97,8 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
9397
if (_betaUpdatesOverride) {
9498
await _checkForBERUpdates(release);
9599
} else {
96-
const String url =
97-
'https://api.github.com/repos/thecontrappostoshop/orion/releases/latest';
100+
final String url =
101+
'https://api.github.com/repos/$_repo/orion/releases/latest';
98102
int retryCount = 0;
99103
const int maxRetries = 3;
100104
const int initialDelay = 750;
@@ -169,8 +173,7 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
169173
_logger.warning('release name is empty');
170174
release = 'BRANCH_dev';
171175
}
172-
String url =
173-
'https://api.github.com/repos/thecontrappostoshop/orion/releases';
176+
String url = 'https://api.github.com/repos/$_repo/orion/releases';
174177
int retryCount = 0;
175178
const int maxRetries = 3;
176179
const int initialDelay = 750; // Initial delay in milliseconds
@@ -187,7 +190,7 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
187190
final String latestVersion = releaseItem['tag_name'];
188191
final String commitSha = releaseItem['target_commitish'];
189192
final commitUrl =
190-
'https://api.github.com/repos/thecontrappostoshop/orion/commits/$commitSha';
193+
'https://api.github.com/repos/$_repo/orion/commits/$commitSha';
191194
final commitResponse = await http.get(Uri.parse(commitUrl));
192195
if (commitResponse.statusCode == 200) {
193196
final commitJson = json.decode(commitResponse.body);
@@ -325,6 +328,68 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
325328
);
326329
}
327330

331+
Future<void> launchUpdateDialog() async {
332+
bool shouldUpdate = await showDialog(
333+
barrierDismissible: false,
334+
context: context,
335+
builder: (BuildContext context) {
336+
return GlassAlertDialog(
337+
title: Row(
338+
children: [
339+
PhosphorIcon(
340+
PhosphorIcons.download(),
341+
color: Theme.of(context).colorScheme.primary,
342+
size: 32,
343+
),
344+
const SizedBox(width: 12),
345+
Text(
346+
'Update Orion',
347+
style: TextStyle(
348+
color: Theme.of(context).colorScheme.primary,
349+
),
350+
),
351+
],
352+
),
353+
content: const Text(
354+
'Do you want to update the Orion HMI?\nThis will download the latest version from GitHub.'),
355+
actions: [
356+
GlassButton(
357+
tint: GlassButtonTint.negative,
358+
onPressed: () {
359+
Navigator.of(context).pop(false);
360+
},
361+
style: ElevatedButton.styleFrom(
362+
minimumSize: const Size(0, 60),
363+
),
364+
child: const Text(
365+
'Dismiss',
366+
style: TextStyle(fontSize: 20),
367+
),
368+
),
369+
GlassButton(
370+
tint: GlassButtonTint.positive,
371+
onPressed: () {
372+
Navigator.of(context).pop(true);
373+
},
374+
style: ElevatedButton.styleFrom(
375+
minimumSize: const Size(0, 60),
376+
),
377+
child: const Text(
378+
'Update Now',
379+
style: TextStyle(fontSize: 20),
380+
),
381+
)
382+
],
383+
);
384+
},
385+
);
386+
387+
if (shouldUpdate) {
388+
final provider = Provider.of<OrionUpdateProvider>(context, listen: false);
389+
await provider.performUpdate(context, _assetUrl);
390+
}
391+
}
392+
328393
@override
329394
Widget build(BuildContext context) {
330395
return Scaffold(
@@ -430,6 +495,7 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
430495
children: [
431496
Expanded(
432497
child: GlassButton(
498+
tint: GlassButtonTint.neutral,
433499
onPressed: _viewChangelog,
434500
style: ElevatedButton.styleFrom(
435501
minimumSize: const Size.fromHeight(65),
@@ -465,8 +531,9 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
465531
width: 12), // Add some space between the buttons
466532
Expanded(
467533
child: GlassButton(
534+
tint: GlassButtonTint.positive,
468535
onPressed: () async {
469-
_performUpdate(context);
536+
launchUpdateDialog();
470537
},
471538
style: ElevatedButton.styleFrom(
472539
minimumSize: const Size.fromHeight(65),
@@ -524,7 +591,7 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
524591
),
525592
),
526593
),
527-
// TODO: Placeholder for Odyssey updater - pending API changes
594+
// TODO: Placeholder for Backend / OS updater - pending API changes
528595
GlassCard(
529596
outlined: true,
530597
child: Padding(
@@ -539,7 +606,7 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
539606
size: 30),
540607
const SizedBox(width: 10),
541608
const Text(
542-
'Odyssey Updater',
609+
'Backend Updater',
543610
style: TextStyle(
544611
fontSize: 26,
545612
fontWeight: FontWeight.bold,
@@ -558,196 +625,6 @@ class UpdateScreenState extends State<UpdateScreen> with SafeSetStateMixin {
558625
),
559626
);
560627
}
561-
562-
Future<void> _performUpdate(BuildContext context) async {
563-
final String localUser = Platform.environment['USER'] ?? 'pi';
564-
final String upgradeFolder = '/home/$localUser/orion_upgrade/';
565-
final String downloadPath = '$upgradeFolder/orion_armv7.tar.gz';
566-
final String orionFolder = '/home/$localUser/orion/';
567-
final String newOrionFolder = '/home/$localUser/orion_new/';
568-
final String backupFolder = '/home/$localUser/orion_backup/';
569-
final String scriptPath = '$upgradeFolder/update_orion.sh';
570-
571-
if (_assetUrl.isEmpty) {
572-
_logger.warning('Asset URL is empty');
573-
return;
574-
}
575-
576-
_logger.info('Downloading from $_assetUrl');
577-
578-
// Show the update dialog
579-
_showUpdateDialog(context, 'Starting update...');
580-
581-
try {
582-
// Purge and recreate the upgrade folder
583-
final upgradeDir = Directory(upgradeFolder);
584-
if (await upgradeDir.exists()) {
585-
try {
586-
await upgradeDir.delete(recursive: true);
587-
} catch (e) {
588-
_logger.warning('Could not purge upgrade directory');
589-
}
590-
}
591-
await upgradeDir.create(recursive: true);
592-
593-
final newDir = Directory(newOrionFolder);
594-
if (await newDir.exists()) {
595-
try {
596-
await newDir.delete(recursive: true);
597-
} catch (e) {
598-
_logger.warning('Could not purge new Orion directory');
599-
}
600-
}
601-
await newDir.create(recursive: true);
602-
603-
// Update dialog text
604-
_updateDialogText(context, 'Downloading update file...');
605-
606-
await Future.delayed(const Duration(seconds: 1));
607-
608-
// Download the update file
609-
final response = await http.get(Uri.parse(_assetUrl));
610-
if (response.statusCode == 200) {
611-
final file = File(downloadPath);
612-
await file.writeAsBytes(response.bodyBytes);
613-
614-
// Update dialog text
615-
_updateDialogText(context, 'Extracting update file...');
616-
617-
await Future.delayed(const Duration(seconds: 1));
618-
619-
// Extract the update to the new directory
620-
final extractResult = await Process.run('sudo',
621-
['tar', '--overwrite', '-xzf', downloadPath, '-C', newOrionFolder]);
622-
if (extractResult.exitCode != 0) {
623-
_logger.warning(
624-
'Failed to extract update file: ${extractResult.stderr}');
625-
_dismissUpdateDialog(context);
626-
return;
627-
}
628-
629-
// Create the update script
630-
final scriptContent = '''
631-
#!/bin/bash
632-
633-
# Variables
634-
local_user=$localUser
635-
orion_folder=$orionFolder
636-
new_orion_folder=$newOrionFolder
637-
upgrade_folder=$upgradeFolder
638-
backup_folder=$backupFolder
639-
640-
# If previous backup exists, delete it
641-
if [ -d \$backup_folder ]; then
642-
sudo rm -R \$backup_folder
643-
fi
644-
645-
# Backup the current Orion directory
646-
sudo cp -R \$orion_folder \$backup_folder
647-
648-
# Remove the old Orion directory
649-
sudo rm -R \$orion_folder
650-
651-
# Restore config file
652-
sudo cp \$backup_folder/orion.cfg \$new_orion_folder
653-
654-
# Move the new Orion directory to the original location
655-
sudo mv \$new_orion_folder \$orion_folder
656-
657-
# Delete the upgrade and new folder
658-
sudo rm -R \$upgrade_folder
659-
660-
# Fix permissions
661-
sudo chown -R \$local_user:\$local_user \$orion_folder
662-
663-
# Restart the Orion service
664-
sudo systemctl restart orion.service
665-
''';
666-
667-
final scriptFile = File(scriptPath);
668-
await scriptFile.writeAsString(scriptContent);
669-
await Process.run('chmod', ['+x', scriptPath]);
670-
671-
// Update dialog text
672-
_updateDialogText(context, 'Executing update script...');
673-
674-
await Future.delayed(const Duration(seconds: 2));
675-
676-
// Execute the update script
677-
final result = await Process.run('nohup', ['sudo', scriptPath]);
678-
if (result.exitCode == 0) {
679-
_logger.info('Update script executed successfully');
680-
} else {
681-
_logger.warning('Failed to execute update script: ${result.stderr}');
682-
}
683-
} else {
684-
_logger.warning('Failed to download update file');
685-
}
686-
} catch (e) {
687-
_logger.warning('Update failed: $e');
688-
} finally {
689-
// Dismiss the update dialog
690-
_dismissUpdateDialog(context);
691-
}
692-
}
693-
694-
Future<void> _showUpdateDialog(BuildContext context, String message) {
695-
return showDialog(
696-
context: context,
697-
barrierDismissible: false,
698-
builder: (BuildContext context) {
699-
return SafeArea(
700-
child: Dialog(
701-
backgroundColor: Colors.transparent,
702-
insetPadding: EdgeInsets.zero,
703-
child: Container(
704-
width: MediaQuery.of(context).size.width,
705-
height: MediaQuery.of(context).size.height,
706-
color: Theme.of(context).colorScheme.surface,
707-
child: Column(
708-
mainAxisSize: MainAxisSize.min,
709-
mainAxisAlignment: MainAxisAlignment.center,
710-
children: <Widget>[
711-
const SizedBox(
712-
height: 75,
713-
width: 75,
714-
child: CircularProgressIndicator(
715-
strokeWidth: 6,
716-
),
717-
),
718-
const SizedBox(height: 60),
719-
Text(
720-
message,
721-
textAlign: TextAlign.center,
722-
style: const TextStyle(fontSize: 32),
723-
),
724-
],
725-
),
726-
),
727-
),
728-
);
729-
},
730-
);
731-
}
732-
733-
void _updateDialogText(BuildContext context, String message) {
734-
if (Navigator.of(context).canPop()) {
735-
// Show the new dialog first
736-
_showUpdateDialog(context, message).then((_) {
737-
// Pop the old dialog after the new one has been rendered
738-
if (Navigator.of(context).canPop()) {
739-
Navigator.of(context).pop();
740-
}
741-
});
742-
} else {
743-
// If there's no dialog to pop, just show the new one
744-
_showUpdateDialog(context, message);
745-
}
746-
}
747-
748-
void _dismissUpdateDialog(BuildContext context) {
749-
if (Navigator.of(context).canPop()) {
750-
Navigator.of(context).pop();
751-
}
752-
}
753628
}
629+
// Update flow is handled by OrionUpdateProvider via
630+
// `Provider.of<OrionUpdateProvider>(context, listen: false).performUpdate(...)`.

0 commit comments

Comments
 (0)