feat(shorebird_code_push): track support (#232)

* feat(shorebird_code_push): track support

* cleanup and add todos

* docs

* run ffigen

* use c_char instead of char

* Add channel support

* tests

* tests

* update podfile.lock

* Update example to include tracks selector

---------

Co-authored-by: Bryan Oltman <bryan@shorebird.dev>
Co-authored-by: Bryan Oltman <bryanoltman@gmail.com>
This commit is contained in:
Felix Angelov
2024-11-12 13:58:50 -06:00
committed by GitHub
parent 59bcb311d5
commit ee3f5ec669
13 changed files with 411 additions and 115 deletions
+1 -1
View File
@@ -13,4 +13,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
COCOAPODS: 1.15.2
COCOAPODS: 1.16.1
+96 -8
View File
@@ -29,6 +29,7 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> {
final _updater = ShorebirdUpdater();
late final bool _isUpdaterAvailable;
var _currentTrack = UpdateTrack.stable;
var _isCheckingForUpdates = false;
Patch? _currentPatch;
@@ -54,10 +55,20 @@ class _MyHomePageState extends State<MyHomePage> {
try {
setState(() => _isCheckingForUpdates = true);
// Check if there's an update available.
final status = await _updater.checkForUpdate();
final status = await _updater.checkForUpdate(track: _currentTrack);
if (!mounted) return;
// If there is an update available, show a banner.
if (status == UpdateStatus.outdated) _showUpdateAvailableBanner();
switch (status) {
case UpdateStatus.upToDate:
_showNoUpdateAvailableBanner();
case UpdateStatus.outdated:
_showUpdateAvailableBanner();
case UpdateStatus.restartRequired:
_showRestartBanner();
case UpdateStatus.unavailable:
// Do nothing, there is already a warning displayed at the top of the
// screen.
}
} catch (error) {
// If an error occurs, we log it for now.
debugPrint('Error checking for update: $error');
@@ -88,7 +99,9 @@ class _MyHomePageState extends State<MyHomePage> {
..hideCurrentMaterialBanner()
..showMaterialBanner(
MaterialBanner(
content: const Text('Update available'),
content: Text(
'Update available for the ${_currentTrack.name} track.',
),
actions: [
TextButton(
onPressed: () async {
@@ -104,6 +117,26 @@ class _MyHomePageState extends State<MyHomePage> {
);
}
void _showNoUpdateAvailableBanner() {
ScaffoldMessenger.of(context)
..hideCurrentMaterialBanner()
..showMaterialBanner(
MaterialBanner(
content: Text(
'No update available on the ${_currentTrack.name} track.',
),
actions: [
TextButton(
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentMaterialBanner();
},
child: const Text('Dismiss'),
),
],
),
);
}
void _showRestartBanner() {
ScaffoldMessenger.of(context)
..hideCurrentMaterialBanner()
@@ -145,8 +178,10 @@ class _MyHomePageState extends State<MyHomePage> {
Future<void> _downloadUpdate() async {
_showDownloadingBanner();
try {
// Perform the update (e.g download the latest patch).
await _updater.update();
// Perform the update (e.g download the latest patch on [_currentTrack]).
// Note that [track] is optional. Not passing it will default to the
// stable track.
await _updater.update(track: _currentTrack);
if (!mounted) return;
// Show a banner to inform the user that the update is ready and that they
// need to restart the app.
@@ -166,9 +201,21 @@ class _MyHomePageState extends State<MyHomePage> {
backgroundColor: theme.colorScheme.inversePrimary,
title: const Text('Shorebird Code Push'),
),
body: _isUpdaterAvailable
? _CurrentPatchVersion(patch: _currentPatch)
: const _ShorebirdUnavailable(),
body: Column(
children: [
if (!_isUpdaterAvailable) const _ShorebirdUnavailable(),
const Spacer(),
_CurrentPatchVersion(patch: _currentPatch),
const SizedBox(height: 12),
_TrackPicker(
currentTrack: _currentTrack,
onChanged: (track) {
setState(() => _currentTrack = track);
},
),
const Spacer(),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _isCheckingForUpdates ? null : _checkForUpdate,
tooltip: 'Check for update',
@@ -224,6 +271,47 @@ class _CurrentPatchVersion extends StatelessWidget {
}
}
/// Widget that allows selection of update track.
class _TrackPicker extends StatelessWidget {
const _TrackPicker({
required this.currentTrack,
required this.onChanged,
});
final UpdateTrack currentTrack;
final ValueChanged<UpdateTrack> onChanged;
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Update track:'),
SegmentedButton<UpdateTrack>(
segments: const [
ButtonSegment(
label: Text('Stable'),
value: UpdateTrack.stable,
),
ButtonSegment(
label: Text('Beta'),
icon: Icon(Icons.science),
value: UpdateTrack.beta,
),
ButtonSegment(
label: Text('Staging'),
icon: Icon(Icons.construction),
value: UpdateTrack.staging,
),
],
selected: {currentTrack},
onSelectionChanged: (tracks) => onChanged(tracks.single),
),
],
);
}
}
/// A reusable loading indicator.
class _LoadingIndicator extends StatelessWidget {
const _LoadingIndicator();