diff --git a/.github/workflows/conferenceapp.yaml b/.github/workflows/conferenceapp.yaml index 4942b71..6c104f4 100644 --- a/.github/workflows/conferenceapp.yaml +++ b/.github/workflows/conferenceapp.yaml @@ -36,20 +36,6 @@ env: ENV_BASE64: ${{ secrets.ENV_BASE64 }} jobs: - validate-inputs: - runs-on: ubuntu-latest - steps: - - name: Validate Inputs - run: | - if [[ -z "${{ github.event.inputs['build-version'] }}" ]]; then - echo "Error: build-version is required for a release." - exit 1 - fi - if [[ -z "${{ github.event.inputs['build-number'] }}" ]]; then - echo "Error: build-number is required for a release." - exit 1 - fi - check-formatting: if: github.event_name == 'pull_request' uses: ./.github/workflows/shared-workflow.yaml diff --git a/packages/conferenceapp/ios/Podfile.lock b/packages/conferenceapp/ios/Podfile.lock index 6487cd3..fe72047 100644 --- a/packages/conferenceapp/ios/Podfile.lock +++ b/packages/conferenceapp/ios/Podfile.lock @@ -75,6 +75,8 @@ PODS: - Flutter - FlutterMacOS - PromisesObjC (2.4.0) + - restart_app (0.0.1): + - Flutter - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -92,6 +94,7 @@ DEPENDENCIES: - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - restart_app (from `.symlinks/plugins/restart_app/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -129,6 +132,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" + restart_app: + :path: ".symlinks/plugins/restart_app/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" sqflite_darwin: @@ -159,6 +164,7 @@ SPEC CHECKSUMS: package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + restart_app: 806659942bf932f6ce51c5372f91ce5e81c8c14a shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe diff --git a/packages/conferenceapp/lib/conference_app.dart b/packages/conferenceapp/lib/conference_app.dart index 32650fe..7960c76 100644 --- a/packages/conferenceapp/lib/conference_app.dart +++ b/packages/conferenceapp/lib/conference_app.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'src/features/more/application/theme_vm.dart'; import 'src/features/onboarding/presentation/presentation.dart'; +import 'src/features/updater/widgets/update_listener.dart'; class ConferenceApp extends ConsumerStatefulWidget { const ConferenceApp({super.key}); @@ -54,10 +55,12 @@ class _ConferenceAppState extends ConsumerState { navigatorKey: Devfest2024Router.rootNavigatorKey, initialRoute: Devfest2024Router.initialRoute, onGenerateRoute: Devfest2024Router.instance.onGenerateRoutes, - builder: (context, child) => AccessibilityTools( - minimumTapAreas: const MinimumTapAreas(mobile: 30, desktop: 44), - checkFontOverflows: true, - child: child, + builder: (context, child) => UpdateListener( + child: AccessibilityTools( + minimumTapAreas: const MinimumTapAreas(mobile: 30, desktop: 44), + checkFontOverflows: true, + child: child, + ), ), themeMode: themeMode.theme, theme: ThemeData( diff --git a/packages/conferenceapp/lib/src/features/updater/application/updater_state.dart b/packages/conferenceapp/lib/src/features/updater/application/updater_state.dart new file mode 100644 index 0000000..4b8c3bb --- /dev/null +++ b/packages/conferenceapp/lib/src/features/updater/application/updater_state.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; + +enum UpdaterStatus { idle, updateCheckInProgress, downloadInProgress } + +final class UpdaterState extends Equatable { + const UpdaterState({ + this.status = UpdaterStatus.idle, + this.updateAvailable = false, + this.isNewPatchReadyToInstall = false, + }); + + final UpdaterStatus status; + final bool updateAvailable; + final bool isNewPatchReadyToInstall; + + UpdaterState copyWith({ + UpdaterStatus? status, + bool? updateAvailable, + bool? isNewPatchReadyToInstall, + }) { + return UpdaterState( + status: status ?? this.status, + updateAvailable: updateAvailable ?? this.updateAvailable, + isNewPatchReadyToInstall: + isNewPatchReadyToInstall ?? this.isNewPatchReadyToInstall, + ); + } + + @override + List get props => [ + status, + updateAvailable, + isNewPatchReadyToInstall, + ]; +} diff --git a/packages/conferenceapp/lib/src/features/updater/application/updater_vm.dart b/packages/conferenceapp/lib/src/features/updater/application/updater_vm.dart new file mode 100644 index 0000000..177c9c1 --- /dev/null +++ b/packages/conferenceapp/lib/src/features/updater/application/updater_vm.dart @@ -0,0 +1,98 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:equatable/equatable.dart'; +import 'package:shorebird_code_push/shorebird_code_push.dart'; + +enum UpdaterStatus { + idle, + updateCheckInProgress, + downloadInProgress, +} + +class UpdaterState extends Equatable { + final UpdaterStatus status; + final bool updateAvailable; + final bool isNewPatchReadyToInstall; + + const UpdaterState({ + this.status = UpdaterStatus.idle, + this.updateAvailable = false, + this.isNewPatchReadyToInstall = false, + }); + + UpdaterState copyWith({ + UpdaterStatus? status, + bool? updateAvailable, + bool? isNewPatchReadyToInstall, + }) { + return UpdaterState( + status: status ?? this.status, + updateAvailable: updateAvailable ?? this.updateAvailable, + isNewPatchReadyToInstall: + isNewPatchReadyToInstall ?? this.isNewPatchReadyToInstall, + ); + } + + @override + List get props => + [status, updateAvailable, isNewPatchReadyToInstall]; +} + +class UpdaterNotifier extends StateNotifier { + UpdaterNotifier({ShorebirdCodePush? codePush}) + : _codePush = codePush ?? ShorebirdCodePush(), + super(const UpdaterState()) { + Future.microtask(() => init()); + } + + final ShorebirdCodePush _codePush; + + Future init() async { + await checkForUpdates(); + } + + Future checkForUpdates() async { + state = state.copyWith(status: UpdaterStatus.updateCheckInProgress); + try { + final updateAvailable = await _codePush.isNewPatchAvailableForDownload(); + state = state.copyWith( + status: UpdaterStatus.idle, + updateAvailable: updateAvailable, + ); + if (updateAvailable) await _downloadUpdate(); + } catch (error, _) { + // Handle error (e.g., log it) + state = state.copyWith( + status: UpdaterStatus.idle, + updateAvailable: true, + ); + } + } + + Future _downloadUpdate() async { + state = state.copyWith(status: UpdaterStatus.downloadInProgress); + try { + await _codePush.downloadUpdateIfAvailable(); + } catch (error, _) { + // Handle error (e.g., log it) + } + try { + final isNewPatchReadyToInstall = + await _codePush.isNewPatchReadyToInstall(); + state = state.copyWith( + isNewPatchReadyToInstall: isNewPatchReadyToInstall, + status: UpdaterStatus.idle, + ); + } catch (error, _) { + // Handle error (e.g., log it) + state = state.copyWith( + isNewPatchReadyToInstall: false, + status: UpdaterStatus.idle, + ); + } + } +} + +final updaterProvider = + StateNotifierProvider((ref) { + return UpdaterNotifier(); +}); diff --git a/packages/conferenceapp/lib/src/features/updater/widgets/update_listener.dart b/packages/conferenceapp/lib/src/features/updater/widgets/update_listener.dart new file mode 100644 index 0000000..2572b3c --- /dev/null +++ b/packages/conferenceapp/lib/src/features/updater/widgets/update_listener.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:restart_app/restart_app.dart'; + +import '../application/updater_vm.dart'; + +class UpdateListener extends ConsumerWidget { + const UpdateListener({required this.child, super.key}); + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(updaterProvider, (previous, current) { + if (previous?.status == UpdaterStatus.downloadInProgress && + current.status == UpdaterStatus.idle && + current.isNewPatchReadyToInstall) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context) + ..hideCurrentMaterialBanner() + ..showMaterialBanner( + MaterialBanner( + content: const Text('An update is available!'), + actions: [ + TextButton( + onPressed: Restart.restartApp, + child: const Text('Restart Now'), + ), + ], + ), + ); + }); + } + }); + + return child; + } +} diff --git a/packages/conferenceapp/pubspec.lock b/packages/conferenceapp/pubspec.lock index 0fca73c..daa616b 100644 --- a/packages/conferenceapp/pubspec.lock +++ b/packages/conferenceapp/pubspec.lock @@ -623,6 +623,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + restart_app: + dependency: "direct main" + description: + name: restart_app + sha256: "00d5ec3e9de871cedbe552fc41e615b042b5ec654385e090e0983f6d02f655ed" + url: "https://pub.dev" + source: hosted + version: "1.3.2" riverpod: dependency: transitive description: @@ -703,6 +711,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + shorebird_code_push: + dependency: "direct main" + description: + name: shorebird_code_push + sha256: "77511427c51906dd39d3bb1e6b0a8c49777975f9b0a5d073e35c9087a8a36bb6" + url: "https://pub.dev" + source: hosted + version: "1.1.6" sky_engine: dependency: transitive description: flutter @@ -989,5 +1005,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.5.1 <4.0.0" flutter: ">=3.24.0" diff --git a/packages/conferenceapp/pubspec.yaml b/packages/conferenceapp/pubspec.yaml index 0a4267d..2554fbf 100644 --- a/packages/conferenceapp/pubspec.yaml +++ b/packages/conferenceapp/pubspec.yaml @@ -26,7 +26,9 @@ dependencies: mesh: ^0.4.1 package_info_plus: ^8.0.2 path_provider: ^2.1.4 + restart_app: ^1.3.2 shared_preferences: ^2.3.3 + shorebird_code_push: ^1.1.6 url_launcher: ^6.3.1 dependency_overrides: diff --git a/packages/conferenceapp/shorebird.yaml b/packages/conferenceapp/shorebird.yaml index b956c70..53c2831 100644 --- a/packages/conferenceapp/shorebird.yaml +++ b/packages/conferenceapp/shorebird.yaml @@ -11,4 +11,4 @@ app_id: 85a91995-2ca3-4ce0-bd34-a85590d78140 # If auto_update: false, you will need to use package:shorebird_code_push to trigger updates. # https://pub.dev/packages/shorebird_code_push # Uncomment the following line to disable automatic updates. -# auto_update: false +auto_update: false