diff --git a/.gitignore b/.gitignore index cd48c96..a92f992 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ test/firebase_remote_config_man_up_service_test.mocks.dart .pub-cache/ .pub/ /build/ + + +example/ios +example/android \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7df28..91225f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # manUp +## [9.1.0] + +- `ManUpService` now implements `ChangeNotifier` and the most recent status can be retrieved from the `status` getter +- `onComplete`, `onError` and `shouldShowAlert` on `ManUpWidget` are now optional (`shouldShowAlert` defaults to `() => true`) +- `ManUpWidget` now exposes an optional `onStatusChanged` +- "Kill switch" (`disabled`) and required update alert dialogs are no longer barrier dismissable. +- Changes to status will now show updated dialogs +- Update examples + ## [9.0.1] - Relax version requirements for firebase diff --git a/README.md b/README.md index d523493..1e9c988 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -# Manup +# ManUp + +*Man*datory *Up*dates for Flutter [![pub package](https://img.shields.io/pub/v/manup.svg)](https://pub.dartlang.org/packages/manup) [![Build Status](https://travis-ci.org/NextFaze/flutter_manup.svg?branch=master)](https://travis-ci.org/NextFaze/flutter_manup) [![Coverage Status](https://coveralls.io/repos/github/NextFaze/flutter_manup/badge.svg?branch=master)](https://coveralls.io/github/NextFaze/flutter_manup?branch=master) +![image](./example.gif) + Sometimes you have an app which talks to services in the cloud. Sometimes, those services change, and your app no longer works. Wouldn't it be great if the app could let the user know there's an update? That's what this module diff --git a/example.gif b/example.gif new file mode 100644 index 0000000..b9a0c0c Binary files /dev/null and b/example.gif differ diff --git a/example/.gitignore b/example/.gitignore index 07de144..ae7f20d 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -48,4 +48,5 @@ macos/ windows/ web/ linux/ -firebase_options.dart \ No newline at end of file +firebase_options.dart +.metadata \ No newline at end of file diff --git a/example/.metadata b/example/.metadata deleted file mode 100644 index 5e4a304..0000000 --- a/example/.metadata +++ /dev/null @@ -1,45 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "b0366e0a3f089e15fd89c97604ab402fe26b724c" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - - platform: android - create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - - platform: ios - create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - - platform: linux - create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - - platform: macos - create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - - platform: web - create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - - platform: windows - create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/lib/main.dart b/example/lib/main.dart index 66750d3..8ce3846 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,83 +1,31 @@ -import 'dart:async'; - -import 'package:firebase_core/firebase_core.dart'; -import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart'; import 'package:manup/manup.dart'; +// See /examples for more examples Future main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp( - // You can get DefaultFirebaseOptions from the generated file(firebase_options.dart) - // from flutterfire_cli - - // options: DefaultFirebaseOptions.currentPlatform, - ); - - runApp(const MyApp()); -} - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - @override - State createState() => _MyAppState(); + runApp(const ExampleApp()); } -class _MyAppState extends State { - late FireBaseRemoteConfigManUpService manUpService; - - String statusStr = 'unknown'; - String latestVersion = '-'; - - @override - void initState() { - super.initState(); - manUpService = FireBaseRemoteConfigManUpService( - remoteConfig: FirebaseRemoteConfig.instance, - // Parameter name (key) in remote config - paramName: 'configName', - ); - } +class ExampleApp extends StatelessWidget { + const ExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( - title: 'App', - home: Scaffold( - body: ManUpWidget( - service: manUpService, - shouldShowAlert: () => true, - onComplete: (bool isComplete) => debugPrint('Validate complete'), - onError: (dynamic e) => debugPrint('Error: $e'), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text('Status: $statusStr'), - Text('Latest version: $latestVersion'), - ], - )), - ), - floatingActionButton: FloatingActionButton( - child: const Text('Validate'), - onPressed: () { - manUpService.validate().then((status) { - setState(() { - statusStr = status.name; - latestVersion = manUpService - .setting( - key: 'latest', - orElse: '', - os: 'ios', - ) - .toString(); - }); - }).catchError((error) { - debugPrint('error: $error'); - }); - }, - ), + home: ManUpWidget( + // Alternatively, use [FireBaseRemoteConfigManUpService] or implement + // your own [ManUpService] + service: HttpManUpService('https://example.com/my_config.json', + http: Client()), + child: const Text('My App'), + // Optionals + onComplete: (_) {}, + onError: (_) {}, + onStatusChanged: (status) {}, + shouldShowAlert: () => true, ), ); } diff --git a/example/lib/main_debug.dart b/example/lib/main_debug.dart new file mode 100644 index 0000000..9071b5d --- /dev/null +++ b/example/lib/main_debug.dart @@ -0,0 +1,284 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:manup/manup.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + runApp(const DebugApp()); +} + +class DebugApp extends StatefulWidget { + const DebugApp({super.key}); + + @override + State createState() => _DebugAppState(); +} + +class _DebugAppState extends State { + late final MockPackageInfoProvider provider; + + /// Alternatively, use the built-in [HttpManUpService] or [FireBaseRemoteConfigManUpService] + late final MockManUpService manUpService; + + final versionController = TextEditingController(text: '1.0.0'); + final latestController = TextEditingController(text: '1.0.0'); + final minimumController = TextEditingController(text: '1.0.0'); + bool enabled = true; + + String latestVersion = '-'; + + @override + void initState() { + super.initState(); + + provider = MockPackageInfoProvider(version: '1.0.0'); + manUpService = MockManUpService( + // By default, this would be be based on the current OS + os: 'ios', + // By default, this would be fetched via package_info_plus + packageInfoProvider: provider, + ); + } + + @override + void dispose() { + super.dispose(); + versionController.dispose(); + latestController.dispose(); + minimumController.dispose(); + } + + _updateData() { + provider.version = versionController.text; + manUpService.metadata = Metadata(data: { + 'ios': { + "latest": latestController.text.trim(), + "minimum": minimumController.text.trim(), + "url": "http://example.com", + "enabled": enabled + } + }); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Flexible( + child: MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + backgroundColor: Colors.grey.shade100, + body: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 12, + ), + const Text( + 'Config', + style: TextStyle(fontSize: 18), + ), + const SizedBox( + height: 12, + ), + const Text('Change settings and press "Revalidate"'), + const SizedBox( + height: 12, + ), + Row( + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Latest:'), + TextField( + onChanged: (_) { + _updateData(); + }, + controller: latestController, + ), + ], + ), + ), + const SizedBox( + width: 12, + ), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Minimum:'), + TextField( + onChanged: (_) { + _updateData(); + }, + controller: minimumController, + ), + ], + ), + ), + const SizedBox( + width: 12, + ), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('App version:'), + TextField( + onChanged: (_) { + _updateData(); + }, + controller: versionController, + ), + ], + )) + ], + ), + const SizedBox(height: 12), + const Text('App enabled:'), + Checkbox( + value: enabled, + onChanged: (newValue) { + setState(() { + enabled = newValue ?? true; + _updateData(); + }); + }), + const SizedBox(height: 12), + ElevatedButton( + onPressed: () { + manUpService.validate(); + }, + child: const Text('Re-validate')) + ], + ), + ), + ), + ), + ), + Flexible( + child: Container( + decoration: BoxDecoration( + border: Border.all(width: 2, color: Colors.teal), + ), + child: ExampleApp(manUpService: manUpService), + ), + ), + ], + ); + } +} + +class ExampleApp extends StatefulWidget { + const ExampleApp({super.key, required this.manUpService}); + + final MockManUpService manUpService; + + @override + State createState() => _ExampleAppState(); +} + +class _ExampleAppState extends State { + String statusStr = 'unknown'; + + @override + initState() { + super.initState(); + widget.manUpService.addListener(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + statusStr = widget.manUpService.status?.name ?? 'unknown'; + loading = widget.manUpService.loading; + }); + }); + }); + } + + bool loading = false; + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'App', + debugShowCheckedModeBanner: false, + home: ManUpWidget( + service: widget.manUpService, + shouldShowAlert: () => true, + child: Scaffold( + body: Stack( + alignment: Alignment.center, + children: [ + Center( + child: Column( + children: [ + const SizedBox( + height: 12, + ), + const Text( + 'My App', + style: TextStyle(fontSize: 22), + ), + const SizedBox( + height: 12, + ), + Text('Status: $statusStr'), + ], + )), + if (loading) ...[ + ModalBarrier( + color: Colors.black.withAlpha(100), + ), + const CircularProgressIndicator() + ] + ], + ), + ), + ), + ); + } +} + +class MockPackageInfoProvider extends PackageInfoProvider { + String version; + + MockPackageInfoProvider({required this.version}); + + @override + Future getInfo() async { + return PackageInfo( + appName: 'ManUpTest', + packageName: 'com.example.manUpTest', + version: version, + buildNumber: '123'); + } +} + +class MockManUpService extends ManUpService { + bool loading = false; + MockManUpService({super.os, super.packageInfoProvider}); + + Metadata metadata = Metadata(data: {}); + + @override + Future getMetadata() async { + // Simulate network + await Future.delayed(const Duration(seconds: 1)); + return metadata; + } + + @override + Future validate([Metadata? metadata]) async { + loading = true; + notifyListeners(); + final status = await super.validate(metadata); + loading = false; + notifyListeners(); + return status; + } +} diff --git a/example/lib/main_firebase.dart b/example/lib/main_firebase.dart new file mode 100644 index 0000000..66750d3 --- /dev/null +++ b/example/lib/main_firebase.dart @@ -0,0 +1,84 @@ +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/material.dart'; +import 'package:manup/manup.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + // You can get DefaultFirebaseOptions from the generated file(firebase_options.dart) + // from flutterfire_cli + + // options: DefaultFirebaseOptions.currentPlatform, + ); + + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + late FireBaseRemoteConfigManUpService manUpService; + + String statusStr = 'unknown'; + String latestVersion = '-'; + + @override + void initState() { + super.initState(); + manUpService = FireBaseRemoteConfigManUpService( + remoteConfig: FirebaseRemoteConfig.instance, + // Parameter name (key) in remote config + paramName: 'configName', + ); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'App', + home: Scaffold( + body: ManUpWidget( + service: manUpService, + shouldShowAlert: () => true, + onComplete: (bool isComplete) => debugPrint('Validate complete'), + onError: (dynamic e) => debugPrint('Error: $e'), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Status: $statusStr'), + Text('Latest version: $latestVersion'), + ], + )), + ), + floatingActionButton: FloatingActionButton( + child: const Text('Validate'), + onPressed: () { + manUpService.validate().then((status) { + setState(() { + statusStr = status.name; + latestVersion = manUpService + .setting( + key: 'latest', + orElse: '', + os: 'ios', + ) + .toString(); + }); + }).catchError((error) { + debugPrint('error: $error'); + }); + }, + ), + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 0947d57..93cb916 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: f5628cd9c92ed11083f425fd1f8f1bc60ecdda458c81d73b143aeda036c35fe7 + sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" url: "https://pub.dev" source: hosted - version: "1.3.16" + version: "1.3.35" async: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -69,82 +69,82 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" firebase_analytics: dependency: "direct main" description: name: firebase_analytics - sha256: "0240076090d77045d757aecb090616066d23b343840d4c21074094d6fe40a184" + sha256: dbf1e7ab22cfb1f4a4adb103b46a26276b4edc593d4a78ef6fb942bafc92e035 url: "https://pub.dev" source: hosted - version: "10.8.0" + version: "10.10.7" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: "6d9baa077d16b47ef5f19d982c4fc475597991aa53b0c601216faa3e1cdab45f" + sha256: "3729b74f8cf1d974a27ba70332ecb55ff5ff560edc8164a6469f4a055b429c37" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.10.8" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "89a740249bce9d52a99db4e501be6087ca6749c73c47cff2b174802be10abd81" + sha256: "019cd7eee74254d33fbd2e29229367ce33063516bf6b3258a341d89e3b0f1655" url: "https://pub.dev" source: hosted - version: "0.5.5+12" + version: "0.5.7+7" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "96607c0e829a581c2a483c658f04e8b159964c3bae2730f73297070bc85d40bb" + sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" url: "https://pub.dev" source: hosted - version: "2.24.2" + version: "2.32.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.3.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: d585bdf3c656c3f7821ba1bd44da5f13365d22fcecaf5eb75c4295246aaa83c0 + sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.18.1" firebase_remote_config: dependency: "direct main" description: name: firebase_remote_config - sha256: "60fc92273d1db338a6fad1839c42dedc4ad64f812043acad0cbb200702f5c9ce" + sha256: "653bd94b68e2c4e89eca10db90576101f1024151f39f2d4e7c64ae6a90a5f9c5" url: "https://pub.dev" source: hosted - version: "4.3.8" + version: "4.4.7" firebase_remote_config_platform_interface: dependency: transitive description: name: firebase_remote_config_platform_interface - sha256: "41813ef8dfbc40ef7a59a73f9e5acef2608dbcb2933241b6c03d52e90677040f" + sha256: "24a2c445b15de3af7e4582ebceb2aa9a1e3731d0202cb3e7a1e03012440fa07d" url: "https://pub.dev" source: hosted - version: "1.4.16" + version: "1.4.35" firebase_remote_config_web: dependency: transitive description: name: firebase_remote_config_web - sha256: "089e92f333c2fb2c05c640c80fecea9d1e06dada0ba85efe34a580987ef94a0a" + sha256: "525aa3000fd27cd023841c802010a06515e564aab2f147aa964b35f54abbf449" url: "https://pub.dev" source: hosted - version: "1.4.16" + version: "1.6.7" flutter: dependency: "direct main" description: flutter @@ -154,10 +154,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" flutter_test: dependency: "direct dev" description: flutter @@ -169,13 +169,13 @@ packages: source: sdk version: "0.0.0" http: - dependency: transitive + dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: @@ -184,30 +184,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -230,7 +222,7 @@ packages: path: ".." relative: true source: path - version: "9.0.1" + version: "9.1.0" matcher: dependency: transitive description: @@ -243,34 +235,34 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" package_info_plus: - dependency: transitive + dependency: "direct main" description: name: package_info_plus - sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.0.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" path: dependency: transitive description: @@ -283,26 +275,26 @@ packages: dependency: transitive description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -323,18 +315,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -400,10 +392,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" typed_data: dependency: transitive description: @@ -416,66 +408,66 @@ packages: dependency: transitive description: name: url_launcher - sha256: d25bb0ca00432a5e1ee40e69c36c85863addf7cc45e433769d61bed3fe81fd96 + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.3" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: cdb7b6da34483f9b2c9f8b2b29bc468fa7271d92e2021607ca0c4d3bcb04cdd4 + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.3" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" vector_math: dependency: transitive description: @@ -488,26 +480,26 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" win32: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.5.4" xdg_directories: dependency: transitive description: @@ -517,5 +509,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 790e2e5..9057661 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -39,6 +39,8 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 firebase_core: ^2.24.2 + package_info_plus: ^8.0.2 + http: ^1.2.2 dev_dependencies: flutter_test: diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1 @@ +void main() {} diff --git a/lib/src/man_up_service.dart b/lib/src/man_up_service.dart index 3a4fd41..a8dc81c 100644 --- a/lib/src/man_up_service.dart +++ b/lib/src/man_up_service.dart @@ -1,17 +1,23 @@ part of manup; -abstract class ManUpService { +abstract class ManUpService with ChangeNotifier { final PackageInfoProvider packageInfoProvider; final ConfigStorage fileStorage; final String? os; + + bool closed = false; + Metadata _manUpData = Metadata(); PlatformData? get configData => os != null ? this.getPlatformData(os!, _manUpData) : null; ManUpDelegate? delegate; + /// The most recent status - use [addListener] to get notified of changes + ManUpStatus? status; + ManUpService({ this.packageInfoProvider = const DefaultPackageInfoProvider(), String? os, @@ -29,32 +35,33 @@ abstract class ManUpService { this._handleManUpStatus(status); return status; } catch (e) { - throw e; + rethrow; } finally { await _storeManUpFile(); } } Future _validate([Metadata? metadata]) async { - PackageInfo info = await this.packageInfoProvider.getInfo(); - metadata ??= await this.getMetadata(); - _manUpData = metadata; - PlatformData? platformData = configData; + try { + PackageInfo info = await this.packageInfoProvider.getInfo(); + metadata ??= await this.getMetadata(); + _manUpData = metadata; + PlatformData? platformData = configData; - if (platformData == null) { - return ManUpStatus.latest; - } - if (!platformData.enabled) { - return ManUpStatus.disabled; - } + if (platformData == null) { + return ManUpStatus.latest; + } + if (!platformData.enabled) { + return ManUpStatus.disabled; + } - try { Version currentVersion = Version.parse(info.version); VersionConstraint latestVersion = VersionConstraint.parse('>=${platformData.latestVersion}'); VersionConstraint minVersion = VersionConstraint.parse('>=${platformData.minVersion}'); - if (latestVersion.allows(currentVersion)) { + if (latestVersion.allows(currentVersion) && + minVersion.allows(currentVersion)) { return ManUpStatus.latest; } else if (minVersion.allows(currentVersion)) { return ManUpStatus.supported; @@ -106,9 +113,13 @@ abstract class ManUpService { Future getMetadata(); // manUp status validation - void _handleManUpStatus(ManUpStatus status) { - this.delegate?.manUpStatusChanged(status); - switch (status) { + void _handleManUpStatus(ManUpStatus newStatus) { + if (closed) return; + + status = newStatus; + notifyListeners(); + this.delegate?.manUpStatusChanged(newStatus); + switch (newStatus) { case ManUpStatus.supported: this.delegate?.manUpUpdateAvailable(); break; @@ -136,7 +147,6 @@ abstract class ManUpService { } } - /// manUp file storage Future _storeManUpFile() async { if (kIsWeb) return; try { @@ -157,8 +167,9 @@ abstract class ManUpService { return Metadata(data: json); } - /// call this on dispose. void close() { + closed = true; + // Stop emitting further events to the delegate (if it exists) this.delegate = null; } } diff --git a/lib/src/metadata.dart b/lib/src/metadata.dart index 0ade19f..52cc948 100644 --- a/lib/src/metadata.dart +++ b/lib/src/metadata.dart @@ -85,12 +85,12 @@ extension MetaDataMessages on Metadata { String get unsupportedMessage => setting( key: 'unsupportedMessage', orElse: - 'This version is no longer supported. Please update to the latest version', + 'This version is no longer supported. Please update to the latest version.', ); // maintenance mode String get disabledMessage => setting( key: 'disabledMessage', orElse: - 'The app is currently in maintenance, please check again shortly', + 'The app is currently in maintenance, please check again shortly.', ); } diff --git a/lib/src/mixin/man_up_dialog_mixin.dart b/lib/src/mixin/man_up_dialog_mixin.dart index 2e04e01..fbf0a1f 100644 --- a/lib/src/mixin/man_up_dialog_mixin.dart +++ b/lib/src/mixin/man_up_dialog_mixin.dart @@ -7,38 +7,43 @@ mixin DialogMixin on State { canLaunch ? launchUrl(Uri.parse(uri)) : Future.value(canLaunch)); } - /// It will emit `true` if updateLater is selected Future showManUpDialog( ManUpStatus status, String? message, String? updateUrl, ) async { - ManUpAppDialog _dialog = ManUpAppDialog(); switch (status) { case ManUpStatus.latest: return Future.value(true); case ManUpStatus.supported: - return _dialog - .showAlertDialog( + return ManUpAppDialog.showAlertDialog( context: context, message: message, trueText: "Update", falseText: "Later") .then((shouldUpdate) => - shouldUpdate! ? _launchUrl(updateUrl!) : false) + shouldUpdate == true ? _launchUrl(updateUrl!) : false) .then((isLaunched) => !(isLaunched as bool)); case ManUpStatus.unsupported: - return _dialog - .showAlertDialog( - context: context, message: message, trueText: "Update") - .then((_) => _launchUrl(updateUrl!)) - .then((_) => false); + return ManUpAppDialog.showAlertDialog( + barrierDismissible: false, + context: context, + message: message, + trueText: "Update") + .then((shouldLaunch) { + if (shouldLaunch == true) { + _launchUrl(updateUrl!); + } + }).then((_) => false); case ManUpStatus.disabled: - return _dialog - .showAlertDialog(context: context, message: message) - .then((_) => exit(0)) - .then(((_) => false)); + return ManUpAppDialog.showAlertDialog( + barrierDismissible: false, context: context, message: message) + .then((shouldClose) { + if (shouldClose == true) { + exit(0); + } + }).then(((_) => false)); } } } diff --git a/lib/src/ui/man_up_app_dialog.dart b/lib/src/ui/man_up_app_dialog.dart index 16436f7..071dfd4 100644 --- a/lib/src/ui/man_up_app_dialog.dart +++ b/lib/src/ui/man_up_app_dialog.dart @@ -1,7 +1,7 @@ part of manup; class ManUpAppDialog { - Future showAlertDialog( + static Future showAlertDialog( {String? message, String trueText = "OK", String? falseText, @@ -13,7 +13,8 @@ class ManUpAppDialog { context: context, builder: (BuildContext context) { return PopScope( - onPopInvoked: (didPop) => Future.value(barrierDismissible), + onPopInvokedWithResult: (didPop, result) => + Future.value(barrierDismissible), child: AlertDialog( title: Text(message ?? ""), actions: [ diff --git a/lib/src/ui/man_up_widget.dart b/lib/src/ui/man_up_widget.dart index 41e78c5..a295661 100644 --- a/lib/src/ui/man_up_widget.dart +++ b/lib/src/ui/man_up_widget.dart @@ -3,18 +3,20 @@ part of manup; class ManUpWidget extends StatefulWidget { final ManUpService service; final Widget child; - final bool Function() shouldShowAlert; - final void Function(bool) onComplete; - final void Function(dynamic) onError; + final bool Function()? shouldShowAlert; + final void Function(bool)? onComplete; + final void Function(dynamic e)? onError; + final void Function(ManUpStatus status)? onStatusChanged; - ManUpWidget( - {Key? key, - required this.child, - required this.service, - required this.shouldShowAlert, - required this.onComplete, - required this.onError}) - : super(key: key); + ManUpWidget({ + Key? key, + required this.child, + required this.service, + this.shouldShowAlert, + this.onComplete, + this.onError, + this.onStatusChanged, + }) : super(key: key); @override _ManUpWidgetState createState() => _ManUpWidgetState(); @@ -26,7 +28,7 @@ class _ManUpWidgetState extends State ManUpDelegateMixin, DialogMixin, WidgetsBindingObserver { - bool isShowingManUpAlert = false; + ManUpStatus? alertDialogType; @override void initState() { @@ -43,32 +45,46 @@ class _ManUpWidgetState extends State try { await widget.service.validate(); } catch (error) { - widget.onError(error); + widget.onError?.call(error); } } - bool get shouldShowManUpAlert => this.widget.shouldShowAlert.call(); + bool get shouldShowManUpAlert => this.widget.shouldShowAlert?.call() ?? true; @override void manUpStatusChanged(ManUpStatus status) { + widget.onStatusChanged?.call(status); + + // Already showing a dialog for this status - nothing to do + if (alertDialogType == status) { + return; + } + // Showing an alert dialog for a different status - close it to show the new status + if (alertDialogType != null) { + Navigator.of(context, rootNavigator: true).pop(); + alertDialogType = null; + } + if (status == ManUpStatus.latest) { - this.widget.onComplete.call(true); + this.widget.onComplete?.call(true); return; } + final updateUrl = widget.service.configData?.updateUrl; if (this.shouldShowManUpAlert) { final message = widget.service.getMessage(forStatus: status); - isShowingManUpAlert = true; - showManUpDialog(status, message, updateUrl) - .then((isUpdateLater) => - isUpdateLater ? this.widget.onComplete.call(true) : isUpdateLater) - .then((_) => isShowingManUpAlert = false); + alertDialogType = status; + showManUpDialog(status, message, updateUrl).then((isUpdateLater) { + alertDialogType = null; + if (isUpdateLater) this.widget.onComplete?.call(true); + return false; + }); } } @override void didChangeAppLifecycleState(AppLifecycleState state) { - if (!isShowingManUpAlert && state == AppLifecycleState.resumed) { + if (state == AppLifecycleState.resumed) { validateManUp(); } } diff --git a/pubspec.lock b/pubspec.lock index 3b85810..3d23ed2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,34 +5,39 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "72.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: f5628cd9c92ed11083f425fd1f8f1bc60ecdda458c81d73b143aeda036c35fe7 + sha256: "5fdcea390499dd26c808a3c662df5f4208d6bbc0643072eee94f1476249e2818" url: "https://pub.dev" source: hosted - version: "1.3.16" + version: "1.3.43" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.7.0" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -53,10 +58,10 @@ packages: dependency: transitive description: name: build - sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" build_config: dependency: transitive description: @@ -69,34 +74,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "5e1929ad37d48bd382b124266cb8e521de5548d406a45a5ae6656c13dab73e37" + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.12" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.2.10" + version: "7.3.2" built_collection: dependency: transitive description: @@ -109,10 +114,10 @@ packages: dependency: transitive description: name: built_value - sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.6.1" + version: "8.9.2" characters: dependency: transitive description: @@ -141,10 +146,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.10.0" collection: dependency: transitive description: @@ -165,18 +170,18 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" dart_style: dependency: transitive description: name: dart_style - sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.7" fake_async: dependency: transitive description: @@ -189,90 +194,90 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" firebase_analytics: dependency: "direct main" description: name: firebase_analytics - sha256: "0240076090d77045d757aecb090616066d23b343840d4c21074094d6fe40a184" + sha256: "9c52c099e9cbb852c7f1d2302c7eb34a15758834eca1877f7a779e6082f9882d" url: "https://pub.dev" source: hosted - version: "10.8.0" + version: "11.3.2" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: "6d9baa077d16b47ef5f19d982c4fc475597991aa53b0c601216faa3e1cdab45f" + sha256: "4ec57aee951832fdbf10ca722bbb83fe0001d6168d6c4cfea9ccee0df6afb1e0" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "4.2.4" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "89a740249bce9d52a99db4e501be6087ca6749c73c47cff2b174802be10abd81" + sha256: "95c594fb1e8960992a607b135459e2f9ea3683dd8d01e6b845cace7c6665ec7e" url: "https://pub.dev" source: hosted - version: "0.5.5+12" + version: "0.5.10+1" firebase_core: dependency: transitive description: name: firebase_core - sha256: "96607c0e829a581c2a483c658f04e8b159964c3bae2730f73297070bc85d40bb" + sha256: c7de9354eb2cd8bfe8059e1112174c9a58beda7051807207306bc48283277cfb url: "https://pub.dev" source: hosted - version: "2.24.2" + version: "3.5.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.3.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: d585bdf3c656c3f7821ba1bd44da5f13365d22fcecaf5eb75c4295246aaa83c0 + sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.18.1" firebase_remote_config: dependency: "direct main" description: name: firebase_remote_config - sha256: "60fc92273d1db338a6fad1839c42dedc4ad64f812043acad0cbb200702f5c9ce" + sha256: "0c13662ce9cb8a5b78ae67d3d775bc2690bffb0fd040f6735f2c9d13cf989693" url: "https://pub.dev" source: hosted - version: "4.3.8" + version: "5.1.2" firebase_remote_config_platform_interface: dependency: transitive description: name: firebase_remote_config_platform_interface - sha256: "41813ef8dfbc40ef7a59a73f9e5acef2608dbcb2933241b6c03d52e90677040f" + sha256: "1748d672c9d9f460b11ec17e10c6afd844a95e26c83ed8cb99c3227511f5038e" url: "https://pub.dev" source: hosted - version: "1.4.16" + version: "1.4.43" firebase_remote_config_web: dependency: transitive description: name: firebase_remote_config_web - sha256: "089e92f333c2fb2c05c640c80fecea9d1e06dada0ba85efe34a580987ef94a0a" + sha256: "2d036ed39a7cbb5e42a4b019c62c1a05ea7327142751553a0c3fa915f5a567db" url: "https://pub.dev" source: hosted - version: "1.4.16" + version: "1.7.1" fixnum: dependency: transitive description: @@ -300,10 +305,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -316,18 +321,18 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http: dependency: "direct main" description: name: http - sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -356,34 +361,34 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -400,6 +405,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -412,26 +425,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: "direct main" description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" mockito: dependency: "direct dev" description: @@ -452,18 +465,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.0.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" path: dependency: transitive description: @@ -476,66 +489,66 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.4.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.8" pool: dependency: transitive description: @@ -544,14 +557,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: "direct main" description: @@ -564,10 +569,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" shelf: dependency: transitive description: @@ -580,10 +585,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -593,10 +598,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.5.0" source_span: dependency: transitive description: @@ -649,10 +654,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timing: dependency: transitive description: @@ -673,66 +678,66 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.1.11" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51 + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.0.35" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.2" vector_math: dependency: transitive description: @@ -745,10 +750,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: @@ -761,34 +766,42 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" win32: dependency: transitive description: name: win32 - sha256: "7dacfda1edcca378031db9905ad7d7bd56b29fd1a90b0908b71a52a12c41e36b" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.5.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.4" yaml: dependency: transitive description: @@ -798,5 +811,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3936e1a..5a43bfe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: manup description: Mandatory update for Flutter Apps that prompts or forces app update by querying a hosted JSON file. -version: 9.0.1 +version: 9.1.0 homepage: https://github.com/NextFaze/flutter_manup environment: diff --git a/test/man_up_dialog_test.dart b/test/man_up_dialog_test.dart new file mode 100644 index 0000000..15b7404 --- /dev/null +++ b/test/man_up_dialog_test.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:manup/manup.dart'; + +class DialogExample extends StatefulWidget { + const DialogExample({super.key, required this.status}); + + final ManUpStatus status; + + @override + State createState() => _DialogExampleState(); +} + +class _DialogExampleState extends State with DialogMixin { + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: () { + showManUpDialog(widget.status, 'This is the dialog', 'example.com'); + }, + child: Text('Show Dialog')); + } +} + +void main() { + buildTestCase(WidgetTester tester, ManUpStatus status) async { + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: DialogExample(status: status), + ))); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + } + + testWidgets('shows no dialog for latest status', (tester) async { + await buildTestCase(tester, ManUpStatus.latest); + + expect(find.byType(AlertDialog), findsNothing); + }); + testWidgets('shows an update dialog for an optional update', (tester) async { + await buildTestCase(tester, ManUpStatus.supported); + + expect(find.byType(AlertDialog), findsOneWidget); + expect( + find.descendant( + of: find.byType(AlertDialog), matching: find.byType(TextButton)), + findsNWidgets(2)); + expect(find.text('This is the dialog'), findsOneWidget); + expect(find.text('Update'), findsOneWidget); + expect(find.text('Later'), findsOneWidget); + }); + + testWidgets('shows an update dialog for a required update', (tester) async { + await buildTestCase(tester, ManUpStatus.unsupported); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text('This is the dialog'), findsOneWidget); + expect( + find.descendant( + of: find.byType(AlertDialog), matching: find.byType(TextButton)), + findsOneWidget); + expect(find.text('Update'), findsOneWidget); + expect(find.text('Later'), findsNothing); + }); + + testWidgets('shows a kill switch dialog', (tester) async { + await buildTestCase(tester, ManUpStatus.disabled); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text('This is the dialog'), findsOneWidget); + expect( + find.descendant( + of: find.byType(AlertDialog), matching: find.byType(TextButton)), + findsOneWidget); + expect(find.text('Update'), findsNothing); + expect(find.text('Later'), findsNothing); + expect(find.text('OK'), findsOneWidget); + }); +} diff --git a/test/man_up_widget_test.dart b/test/man_up_widget_test.dart new file mode 100644 index 0000000..4126574 --- /dev/null +++ b/test/man_up_widget_test.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:manup/manup.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +void main() { + const os = 'ios'; + + Future buildTestCase(WidgetTester tester, + {Metadata? metadata, String? version}) async { + final manUpService = MockManUpService( + os: os, + packageInfoProvider: + MockPackageInfoProvider(version: version ?? '0.0.0')); + + if (metadata != null) { + manUpService.metadata = metadata; + } + await tester.pumpWidget(MaterialApp( + home: ManUpWidget(child: Container(), service: manUpService), + )); + return manUpService; + } + + testWidgets('shows nothing by default', (tester) async { + await buildTestCase(tester); + + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsNothing); + }); + + testWidgets('shows nothing for "latest" status', (tester) async { + await buildTestCase(tester, + version: '2.4.1', + metadata: Metadata(data: { + os: { + "latest": "2.4.1", + "minimum": "2.1.0", + "url": "http://example.com/myAppUpdate", + "enabled": true + }, + })); + + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsNothing); + }); + + testWidgets('shows dialog for optional update', (tester) async { + await buildTestCase(tester, + version: '2.4.0', + metadata: Metadata(data: { + os: { + "latest": "2.4.1", + "minimum": "2.1.0", + "url": "http://example.com/myAppUpdate", + "enabled": true + }, + })); + + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text('There is an update available.'), findsOneWidget); + }); + + testWidgets('shows dialog for required update', (tester) async { + await buildTestCase(tester, + version: '2.0.0', + metadata: Metadata(data: { + os: { + "latest": "2.4.1", + "minimum": "2.1.0", + "url": "http://example.com/myAppUpdate", + "enabled": true + }, + })); + + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); + expect( + find.text( + 'This version is no longer supported. Please update to the latest version.'), + findsOneWidget); + }); + + testWidgets('shows dialog for kill-switch mode', (tester) async { + await buildTestCase(tester, + version: '2.0.0', + metadata: Metadata(data: { + os: { + "latest": "2.4.1", + "minimum": "2.1.0", + "url": "http://example.com/myAppUpdate", + "enabled": false + }, + })); + + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); + expect( + find.text( + 'The app is currently in maintenance, please check again shortly.'), + findsOneWidget); + }); + + testWidgets('re-fetches on lifecycle change', (tester) async { + final service = await buildTestCase(tester); + + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsNothing); + + service.metadata = Metadata(data: { + os: { + "latest": "2.4.1", + "minimum": "2.1.0", + "url": "http://example.com/myAppUpdate", + "enabled": false + }, + }); + + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive); + await tester.pumpAndSettle(); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsOneWidget); + expect( + find.text( + 'The app is currently in maintenance, please check again shortly.'), + findsOneWidget); + }); + + testWidgets('shows an updated dialog when metadata changes', (tester) async { + final service = await buildTestCase(tester, + version: '2.0.0', + metadata: Metadata(data: { + os: { + "latest": "2.4.1", + "minimum": "2.1.0", + "url": "http://example.com/myAppUpdate", + "enabled": true + }, + })); + + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); + expect( + find.text( + 'This version is no longer supported. Please update to the latest version.'), + findsOneWidget); + + service.metadata = Metadata(data: { + os: { + "latest": "2.4.1", + "minimum": "2.1.0", + "url": "http://example.com/myAppUpdate", + "enabled": false + }, + }); + + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive); + await tester.pumpAndSettle(); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsOneWidget); + expect( + find.text( + 'The app is currently in maintenance, please check again shortly.'), + findsOneWidget); + }); +} + +class MockPackageInfoProvider extends PackageInfoProvider { + final String version; + + MockPackageInfoProvider({required this.version}); + + Future getInfo() async { + return PackageInfo( + appName: 'ManUpTest', + packageName: 'com.example.manUpTest', + version: version, + buildNumber: '123'); + } +} + +class MockManUpService extends ManUpService { + MockManUpService({super.os, super.packageInfoProvider}); + + Metadata metadata = Metadata(data: {}); + + @override + Future getMetadata() async => metadata; +}