@_nazmiforreal/flutter-ota 0.1.26 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/flutter-ota-linux-x64 +0 -0
- package/bin/flutter-ota.js +12 -11
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +46 -13
- package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -6
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +2 -0
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +7 -7
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -1
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/config.dart +5 -5
- package/dart-src/packages/cli-tools/lib/src/operations.dart +2 -2
- package/dart-src/packages/cli-tools/pubspec.yaml +8 -21
- package/dart-src/packages/cli-tools/test/backends_test.dart +3 -3
- package/dart-src/packages/cli-tools/test/cli_test.dart +1 -1
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +4 -1
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +1 -1
- package/dart-src/root/README.md +394 -0
- package/dart-src/root/analysis_options.yaml +21 -0
- package/dart-src/root/android/build.gradle +116 -0
- package/dart-src/root/android/consumer-rules.pro +23 -0
- package/dart-src/root/android/settings.gradle +1 -0
- package/dart-src/root/android/src/main/AndroidManifest.xml +21 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt +52 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BlacklistStore.kt +154 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BootDiagnosticStore.kt +150 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/CrashGuard.kt +271 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherApplication.kt +201 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherAutoInitProvider.kt +93 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +245 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt +298 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +1579 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +173 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/SignatureVerifier.kt +177 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt +61 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt +25 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatchManagerTest.kt +401 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfigTest.kt +30 -0
- package/dart-src/root/bin/pack.dart +182 -0
- package/dart-src/root/lib/flutter_ota_kit.dart +1170 -0
- package/dart-src/root/lib/src/app_version_resolver.dart +36 -0
- package/dart-src/root/lib/src/aws_update_source.dart +97 -0
- package/dart-src/root/lib/src/blacklist.dart +54 -0
- package/dart-src/root/lib/src/boot_diagnostic.dart +138 -0
- package/dart-src/root/lib/src/cloudflare_update_source.dart +97 -0
- package/dart-src/root/lib/src/flutter_ota_app.dart +69 -0
- package/dart-src/root/lib/src/io.dart +48 -0
- package/dart-src/root/lib/src/io_stub.dart +19 -0
- package/dart-src/root/lib/src/ota_progress_overlay.dart +987 -0
- package/dart-src/root/lib/src/pack/pack.dart +434 -0
- package/dart-src/root/lib/src/patch_info.dart +12 -0
- package/dart-src/root/lib/src/patcher_channel.dart +160 -0
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_database.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_s3_client.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage_profile.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/with_cloudfront_signed_url.dart +1 -1
- package/dart-src/root/lib/src/pkg/client/flutter_ota_kit_client.dart +1 -0
- package/dart-src/root/lib/src/pkg/client/src/server_update_source.dart +46 -0
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/cloudflare_worker_database.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_build_where.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_bundle_mapper.dart +3 -3
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database_plugin.dart +2 -2
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_s3_client.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage_profile.dart +1 -1
- package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/status.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/flutter_ota_kit_plugin_core.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_blob_database_plugin.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/filter_compatible_app_versions.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/get_update_info.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/request_update_bundle_state.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/resolve_update_info_from_bundles.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/types.dart +2 -2
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_database.dart +26 -15
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_storage.dart +1 -1
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_database.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_get_update_info.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_storage.dart +1 -1
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_types.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_database.dart +2 -2
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_database.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_storage.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_storage.dart +1 -1
- package/dart-src/root/lib/src/pocketbase_update_source.dart +107 -0
- package/dart-src/root/lib/src/postgres_update_source.dart +95 -0
- package/dart-src/root/lib/src/shared_update_check.dart +131 -0
- package/dart-src/root/lib/src/supabase_update_source.dart +93 -0
- package/dart-src/root/pubspec.yaml +54 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +17 -13
- package/scripts/vendor.sh +22 -7
- package/dart-src/packages/core/CHANGELOG.md +0 -21
- package/dart-src/packages/core/README.md +0 -23
- package/dart-src/packages/core/example/lib/main.dart +0 -69
- package/dart-src/packages/core/pubspec.yaml +0 -13
- package/dart-src/packages/core/test/rollout_test.dart +0 -135
- package/dart-src/packages/core/test/semver_test.dart +0 -55
- package/dart-src/packages/core/test/semver_vectors.json +0 -1
- package/dart-src/packages/core/test/types_test.dart +0 -131
- package/dart-src/packages/core/test/uuid_test.dart +0 -40
- package/dart-src/packages/core/tool/dbg.dart +0 -7
- package/dart-src/plugins/aws/.gitkeep +0 -0
- package/dart-src/plugins/aws/CHANGELOG.md +0 -23
- package/dart-src/plugins/aws/LICENSE +0 -21
- package/dart-src/plugins/aws/README.md +0 -28
- package/dart-src/plugins/aws/example/lib/main.dart +0 -60
- package/dart-src/plugins/aws/pubspec.yaml +0 -30
- package/dart-src/plugins/aws/test/aws_database_test.dart +0 -161
- package/dart-src/plugins/aws/test/aws_storage_test.dart +0 -73
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +0 -78
- package/dart-src/plugins/cloudflare/.gitkeep +0 -0
- package/dart-src/plugins/cloudflare/CHANGELOG.md +0 -23
- package/dart-src/plugins/cloudflare/LICENSE +0 -21
- package/dart-src/plugins/cloudflare/README.md +0 -25
- package/dart-src/plugins/cloudflare/example/lib/main.dart +0 -62
- package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +0 -16
- package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +0 -8
- package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +0 -38
- package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +0 -11
- package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +0 -24
- package/dart-src/plugins/cloudflare/pubspec.yaml +0 -28
- package/dart-src/plugins/cloudflare/sql/bundles.sql +0 -42
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +0 -126
- package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +0 -106
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +0 -200
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +0 -261
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +0 -78
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +0 -74
- package/dart-src/plugins/plugin-core/CHANGELOG.md +0 -20
- package/dart-src/plugins/plugin-core/LICENSE +0 -21
- package/dart-src/plugins/plugin-core/README.md +0 -21
- package/dart-src/plugins/plugin-core/example/lib/main.dart +0 -61
- package/dart-src/plugins/plugin-core/pubspec.yaml +0 -23
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +0 -170
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +0 -74
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +0 -345
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +0 -843
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +0 -298
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +0 -195
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +0 -56
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +0 -1589
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +0 -283
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +0 -202
- package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +0 -86
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +0 -88
- package/dart-src/plugins/pocketbase/CHANGELOG.md +0 -23
- package/dart-src/plugins/pocketbase/LICENSE +0 -21
- package/dart-src/plugins/pocketbase/README.md +0 -54
- package/dart-src/plugins/pocketbase/example/lib/main.dart +0 -62
- package/dart-src/plugins/pocketbase/pubspec.yaml +0 -26
- package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +0 -12
- package/dart-src/plugins/postgres/.gitkeep +0 -0
- package/dart-src/plugins/postgres/CHANGELOG.md +0 -23
- package/dart-src/plugins/postgres/LICENSE +0 -21
- package/dart-src/plugins/postgres/README.md +0 -29
- package/dart-src/plugins/postgres/example/lib/main.dart +0 -67
- package/dart-src/plugins/postgres/pubspec.yaml +0 -26
- package/dart-src/plugins/postgres/sql/bundles.sql +0 -45
- package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +0 -21
- package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +0 -171
- package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +0 -126
- package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +0 -125
- package/dart-src/plugins/postgres/sql/hash_user_id.sql +0 -30
- package/dart-src/plugins/postgres/sql/is_device_eligible.sql +0 -237
- package/dart-src/plugins/postgres/sql/prepareSql.ts +0 -11
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +0 -320
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +0 -139
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +0 -210
- package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +0 -60
- package/dart-src/plugins/supabase/CHANGELOG.md +0 -24
- package/dart-src/plugins/supabase/LICENSE +0 -21
- package/dart-src/plugins/supabase/README.md +0 -53
- package/dart-src/plugins/supabase/example/lib/main.dart +0 -63
- package/dart-src/plugins/supabase/pubspec.yaml +0 -27
- package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +0 -44
- package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -968
- package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +0 -232
- package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -137
- package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -206
- package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -186
- package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -505
- package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
- package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
- package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -75
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +0 -654
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +0 -115
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +0 -198
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +0 -168
- /package/dart-src/{packages/core → root}/LICENSE +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/flutter_ota_kit_aws.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_client.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_signer.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_config.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_ssm_client.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/flutter_ota_kit_cloudflare.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_client.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_config.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_config.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/flutter_ota_kit_core.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/app_update_info.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_artifacts.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_patch_artifact.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/changed_asset.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/get_bundles_args.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/metadata.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/patch_info.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/platform.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/rollout.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_range.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_version.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/strategy.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/update_bundle_params.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/uuid.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/asset_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_helpers.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work_store.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/calculate_pagination.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/compression_format.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/content_addressed_assets.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_database_plugin.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_key_builder.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_plugin.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/generate_min_bundle_id.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/is_object.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/legacy_asset_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/paginate_bundles.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/parse_storage_uri.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/query_bundles.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_helpers.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_profile.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/uuidv7.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/flutter_ota_kit_pocketbase.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_client.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_config.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/flutter_ota_kit_postgres.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_client.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_config.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/edge.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/flutter_ota_kit_supabase.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/error_message.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_adapter.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_http.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_config.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_signed_url_batcher.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/types.dart +0 -0
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
|
|
3
|
+
import 'package:flutter/material.dart';
|
|
4
|
+
import 'package:flutter_ota_kit/src/pkg/core/flutter_ota_kit_core.dart'
|
|
5
|
+
show PatchApplyPhase, PatchApplyProgress;
|
|
6
|
+
|
|
7
|
+
/// Snapshot of the forced-update progress overlay's visual state.
|
|
8
|
+
class OtaOverlayState {
|
|
9
|
+
final PatchApplyPhase? phase;
|
|
10
|
+
final double? fraction;
|
|
11
|
+
final String? message;
|
|
12
|
+
final bool hasError;
|
|
13
|
+
final String? errorText;
|
|
14
|
+
final String? targetVersion;
|
|
15
|
+
final String? currentVersion;
|
|
16
|
+
final String? errorHint;
|
|
17
|
+
final String? bundleHash;
|
|
18
|
+
final String? gitCommit;
|
|
19
|
+
final String? commitMessage;
|
|
20
|
+
final String? channel;
|
|
21
|
+
final String? platform;
|
|
22
|
+
final int? bytesPerSec;
|
|
23
|
+
|
|
24
|
+
/// Bytes downloaded so far (downloading phase only).
|
|
25
|
+
final int bytesReceived;
|
|
26
|
+
|
|
27
|
+
/// Total payload size in bytes, or 0 when unknown.
|
|
28
|
+
final int totalBytes;
|
|
29
|
+
|
|
30
|
+
final List<LogLine> logs;
|
|
31
|
+
final bool canRetry;
|
|
32
|
+
|
|
33
|
+
/// Which step index is currently active (0-based). Steps that are done
|
|
34
|
+
/// get a check; the active step gets the braille spinner.
|
|
35
|
+
final int activeStep;
|
|
36
|
+
|
|
37
|
+
const OtaOverlayState({
|
|
38
|
+
this.phase,
|
|
39
|
+
this.fraction,
|
|
40
|
+
this.message,
|
|
41
|
+
this.hasError = false,
|
|
42
|
+
this.errorText,
|
|
43
|
+
this.targetVersion,
|
|
44
|
+
this.currentVersion,
|
|
45
|
+
this.errorHint,
|
|
46
|
+
this.bundleHash,
|
|
47
|
+
this.gitCommit,
|
|
48
|
+
this.commitMessage,
|
|
49
|
+
this.channel,
|
|
50
|
+
this.platform,
|
|
51
|
+
this.bytesPerSec,
|
|
52
|
+
this.bytesReceived = 0,
|
|
53
|
+
this.totalBytes = 0,
|
|
54
|
+
this.logs = const [],
|
|
55
|
+
this.canRetry = false,
|
|
56
|
+
this.activeStep = 0,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
OtaOverlayState copyWith({
|
|
60
|
+
PatchApplyPhase? phase,
|
|
61
|
+
double? fraction,
|
|
62
|
+
String? message,
|
|
63
|
+
Object? hasError = _unset,
|
|
64
|
+
String? errorText,
|
|
65
|
+
String? targetVersion,
|
|
66
|
+
String? currentVersion,
|
|
67
|
+
String? errorHint,
|
|
68
|
+
String? bundleHash,
|
|
69
|
+
String? gitCommit,
|
|
70
|
+
String? channel,
|
|
71
|
+
String? platform,
|
|
72
|
+
int? bytesPerSec,
|
|
73
|
+
int? bytesReceived,
|
|
74
|
+
int? totalBytes,
|
|
75
|
+
List<LogLine>? logs,
|
|
76
|
+
bool? canRetry,
|
|
77
|
+
int? activeStep,
|
|
78
|
+
}) {
|
|
79
|
+
return OtaOverlayState(
|
|
80
|
+
phase: phase ?? this.phase,
|
|
81
|
+
fraction: fraction ?? this.fraction,
|
|
82
|
+
message: message ?? this.message,
|
|
83
|
+
hasError: identical(hasError, _unset) ? this.hasError : hasError as bool,
|
|
84
|
+
errorText: errorText ?? this.errorText,
|
|
85
|
+
targetVersion: targetVersion ?? this.targetVersion,
|
|
86
|
+
currentVersion: currentVersion ?? this.currentVersion,
|
|
87
|
+
errorHint: errorHint ?? this.errorHint,
|
|
88
|
+
bundleHash: bundleHash ?? this.bundleHash,
|
|
89
|
+
gitCommit: gitCommit ?? this.gitCommit,
|
|
90
|
+
commitMessage: commitMessage ?? this.commitMessage,
|
|
91
|
+
channel: channel ?? this.channel,
|
|
92
|
+
platform: platform ?? this.platform,
|
|
93
|
+
bytesPerSec: bytesPerSec ?? this.bytesPerSec,
|
|
94
|
+
bytesReceived: bytesReceived ?? this.bytesReceived,
|
|
95
|
+
totalBytes: totalBytes ?? this.totalBytes,
|
|
96
|
+
logs: logs ?? this.logs,
|
|
97
|
+
canRetry: canRetry ?? this.canRetry,
|
|
98
|
+
activeStep: activeStep ?? this.activeStep,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// A single terminal log line with a color tag and an optional left-column tag.
|
|
104
|
+
///
|
|
105
|
+
/// [text] is the message. If it contains a leading token separated by 2+
|
|
106
|
+
/// spaces (e.g. `'verify passed ✓'`), the overlay renders the first token
|
|
107
|
+
/// in the accent color and the rest in the body color, preserving the
|
|
108
|
+
/// aligned key/value look. Plain lines render whole in [color].
|
|
109
|
+
class LogLine {
|
|
110
|
+
final String color;
|
|
111
|
+
final String text;
|
|
112
|
+
|
|
113
|
+
const LogLine(this.color, this.text);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const Object _unset = Object();
|
|
117
|
+
|
|
118
|
+
/// GitHub-dark inspired palette for the terminal overlay.
|
|
119
|
+
class _C {
|
|
120
|
+
static const bg = Color(0xFF0B0F14);
|
|
121
|
+
static const panel = Color(0xFF11161D);
|
|
122
|
+
static const inner = Color(0xFF0E141B);
|
|
123
|
+
static const head = Color(0xFF161C24);
|
|
124
|
+
static const border = Color(0xFF232A34);
|
|
125
|
+
static const sep = Color(0xFF1B222B);
|
|
126
|
+
static const logBg = Color(0xFF0A0E13);
|
|
127
|
+
static const green = Color(0xFF3FB950);
|
|
128
|
+
static const blue = Color(0xFF58A6FF);
|
|
129
|
+
static const cyan = Color(0xFF56D4DD);
|
|
130
|
+
static const yellow = Color(0xFFD29922);
|
|
131
|
+
static const red = Color(0xFFF85149);
|
|
132
|
+
static const text = Color(0xFFC9D1D9);
|
|
133
|
+
static const dim = Color(0xFF7D8794);
|
|
134
|
+
static const faint = Color(0xFF484F58);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const String _mono = 'monospace';
|
|
138
|
+
|
|
139
|
+
/// The 5 steps shown during a forced update.
|
|
140
|
+
const List<String> _stepLabels = [
|
|
141
|
+
'Initialize',
|
|
142
|
+
'Download bundle',
|
|
143
|
+
'Verify hash + signature',
|
|
144
|
+
'Install patch',
|
|
145
|
+
'Finalize & restart',
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
/// Named color -> swatch, used to resolve [LogLine.color].
|
|
149
|
+
const Map<String, Color> _logColors = {
|
|
150
|
+
'green': _C.green,
|
|
151
|
+
'red': _C.red,
|
|
152
|
+
'yellow': _C.yellow,
|
|
153
|
+
'gray': _C.dim,
|
|
154
|
+
'white': _C.text,
|
|
155
|
+
'cyan': _C.cyan,
|
|
156
|
+
'success': _C.blue,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/// Full-screen, structured terminal-style forced-update overlay.
|
|
160
|
+
///
|
|
161
|
+
/// Sections, top to bottom:
|
|
162
|
+
/// - Title bar (traffic lights + status pill)
|
|
163
|
+
/// - META (channel / version / bundle / size)
|
|
164
|
+
/// - STEPS (per-phase status list)
|
|
165
|
+
/// - PROGRESS (monospace bar + speed / eta / phase)
|
|
166
|
+
/// - LOG (scrolling colored feed)
|
|
167
|
+
/// - Footer (deploy message, or error hint + retry)
|
|
168
|
+
class OtaProgressOverlay extends StatefulWidget {
|
|
169
|
+
final ValueNotifier<OtaOverlayState> state;
|
|
170
|
+
final bool dismissible;
|
|
171
|
+
|
|
172
|
+
/// Called when the user taps the footer `[ retry ]` button in the error
|
|
173
|
+
/// state. When null, the button is shown only if [OtaOverlayState.canRetry].
|
|
174
|
+
final VoidCallback? onRetry;
|
|
175
|
+
|
|
176
|
+
const OtaProgressOverlay({
|
|
177
|
+
super.key,
|
|
178
|
+
required this.state,
|
|
179
|
+
this.dismissible = false,
|
|
180
|
+
this.onRetry,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
@override
|
|
184
|
+
State<OtaProgressOverlay> createState() => _OtaProgressOverlayState();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
188
|
+
int _spinnerIndex = 0;
|
|
189
|
+
Timer? _spinnerTimer;
|
|
190
|
+
|
|
191
|
+
static const _braille = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
192
|
+
|
|
193
|
+
@override
|
|
194
|
+
void initState() {
|
|
195
|
+
super.initState();
|
|
196
|
+
_spinnerTimer = Timer.periodic(const Duration(milliseconds: 80), (_) {
|
|
197
|
+
if (!mounted) return;
|
|
198
|
+
setState(() => _spinnerIndex = (_spinnerIndex + 1) % _braille.length);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@override
|
|
203
|
+
void dispose() {
|
|
204
|
+
_spinnerTimer?.cancel();
|
|
205
|
+
super.dispose();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
String _pct(double? f) =>
|
|
209
|
+
f == null ? '--%' : '${(f.clamp(0.0, 1.0) * 100).toStringAsFixed(0)}%';
|
|
210
|
+
|
|
211
|
+
String _mb(int bytes) => '${(bytes / 1048576).toStringAsFixed(1)} MB';
|
|
212
|
+
|
|
213
|
+
@override
|
|
214
|
+
Widget build(BuildContext context) {
|
|
215
|
+
return ValueListenableBuilder<OtaOverlayState>(
|
|
216
|
+
valueListenable: widget.state,
|
|
217
|
+
builder: (context, s, _) {
|
|
218
|
+
return Material(
|
|
219
|
+
color: _C.bg,
|
|
220
|
+
child: SafeArea(
|
|
221
|
+
child: Padding(
|
|
222
|
+
padding: const EdgeInsets.all(16),
|
|
223
|
+
child: Container(
|
|
224
|
+
decoration: BoxDecoration(
|
|
225
|
+
color: _C.panel,
|
|
226
|
+
borderRadius: BorderRadius.circular(12),
|
|
227
|
+
border: Border.all(color: _C.border),
|
|
228
|
+
),
|
|
229
|
+
child: Column(
|
|
230
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
231
|
+
children: [
|
|
232
|
+
_titleBar(s),
|
|
233
|
+
Expanded(
|
|
234
|
+
child: SingleChildScrollView(
|
|
235
|
+
padding: const EdgeInsets.fromLTRB(16, 14, 16, 14),
|
|
236
|
+
child: Column(
|
|
237
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
238
|
+
children: [
|
|
239
|
+
_sectionLabel('META'),
|
|
240
|
+
const SizedBox(height: 8),
|
|
241
|
+
_metaBlock(s),
|
|
242
|
+
const SizedBox(height: 18),
|
|
243
|
+
_sectionLabel('STEPS'),
|
|
244
|
+
const SizedBox(height: 8),
|
|
245
|
+
_stepsBlock(s),
|
|
246
|
+
const SizedBox(height: 18),
|
|
247
|
+
_sectionLabel('PROGRESS'),
|
|
248
|
+
const SizedBox(height: 8),
|
|
249
|
+
_progressBlock(s),
|
|
250
|
+
const SizedBox(height: 18),
|
|
251
|
+
_sectionLabel('LOG'),
|
|
252
|
+
const SizedBox(height: 8),
|
|
253
|
+
_logBlock(s),
|
|
254
|
+
],
|
|
255
|
+
),
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
_footer(s),
|
|
259
|
+
],
|
|
260
|
+
),
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── Title bar ─────────────────────────────────────────────────────────
|
|
270
|
+
Widget _titleBar(OtaOverlayState s) => Container(
|
|
271
|
+
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
|
|
272
|
+
decoration: const BoxDecoration(
|
|
273
|
+
color: _C.head,
|
|
274
|
+
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
|
275
|
+
border: Border(bottom: BorderSide(color: _C.border)),
|
|
276
|
+
),
|
|
277
|
+
child: Row(
|
|
278
|
+
children: [
|
|
279
|
+
_dot(_C.red),
|
|
280
|
+
const SizedBox(width: 6),
|
|
281
|
+
_dot(_C.yellow),
|
|
282
|
+
const SizedBox(width: 6),
|
|
283
|
+
_dot(_C.green),
|
|
284
|
+
const SizedBox(width: 12),
|
|
285
|
+
const Flexible(
|
|
286
|
+
child: Text(
|
|
287
|
+
'flutter-ota · forced update',
|
|
288
|
+
overflow: TextOverflow.ellipsis,
|
|
289
|
+
style: TextStyle(
|
|
290
|
+
color: _C.text, fontFamily: _mono, fontSize: 12),
|
|
291
|
+
),
|
|
292
|
+
),
|
|
293
|
+
const SizedBox(width: 8),
|
|
294
|
+
_pill(
|
|
295
|
+
s.hasError ? 'FAILED' : 'RUNNING',
|
|
296
|
+
s.hasError ? _C.red : _C.green,
|
|
297
|
+
),
|
|
298
|
+
],
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
// ── META ──────────────────────────────────────────────────────────────
|
|
303
|
+
Widget _metaBlock(OtaOverlayState s) {
|
|
304
|
+
final rows = <List<String>>[
|
|
305
|
+
if (s.channel != null) ['channel', s.channel!],
|
|
306
|
+
if (s.currentVersion != null && s.targetVersion != null)
|
|
307
|
+
['version', '${s.currentVersion} → ${s.targetVersion}']
|
|
308
|
+
else if (s.targetVersion != null)
|
|
309
|
+
['version', s.targetVersion!],
|
|
310
|
+
if (s.bundleHash != null) ['bundle', s.bundleHash!],
|
|
311
|
+
if (s.totalBytes > 0) ['size', _mb(s.totalBytes)],
|
|
312
|
+
];
|
|
313
|
+
if (rows.isEmpty) rows.add(['status', 'preparing…']);
|
|
314
|
+
return _panelBox(
|
|
315
|
+
child: Column(
|
|
316
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
317
|
+
children: [
|
|
318
|
+
for (final r in rows)
|
|
319
|
+
Padding(
|
|
320
|
+
padding: const EdgeInsets.symmetric(vertical: 2),
|
|
321
|
+
child: RichText(
|
|
322
|
+
text: TextSpan(
|
|
323
|
+
style: const TextStyle(fontFamily: _mono, fontSize: 13),
|
|
324
|
+
children: [
|
|
325
|
+
TextSpan(
|
|
326
|
+
text: '${r[0].padRight(9)} ',
|
|
327
|
+
style: const TextStyle(color: _C.dim)),
|
|
328
|
+
TextSpan(
|
|
329
|
+
text: r[1],
|
|
330
|
+
style: TextStyle(
|
|
331
|
+
color: r[0] == 'version' ? _C.blue : _C.text)),
|
|
332
|
+
],
|
|
333
|
+
),
|
|
334
|
+
),
|
|
335
|
+
),
|
|
336
|
+
],
|
|
337
|
+
),
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ── STEPS ─────────────────────────────────────────────────────────────
|
|
342
|
+
Widget _stepsBlock(OtaOverlayState s) {
|
|
343
|
+
final spinner = _braille[_spinnerIndex];
|
|
344
|
+
return _panelBox(
|
|
345
|
+
child: Column(
|
|
346
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
347
|
+
children: List.generate(_stepLabels.length, (i) {
|
|
348
|
+
final done = i < s.activeStep;
|
|
349
|
+
final active = i == s.activeStep && !s.hasError;
|
|
350
|
+
final failed = i == s.activeStep && s.hasError;
|
|
351
|
+
final pending = i > s.activeStep;
|
|
352
|
+
|
|
353
|
+
final Color color = failed
|
|
354
|
+
? _C.red
|
|
355
|
+
: done
|
|
356
|
+
? _C.green
|
|
357
|
+
: active
|
|
358
|
+
? _C.blue
|
|
359
|
+
: _C.faint;
|
|
360
|
+
|
|
361
|
+
final String glyph = failed
|
|
362
|
+
? '✗'
|
|
363
|
+
: done
|
|
364
|
+
? '✓'
|
|
365
|
+
: active
|
|
366
|
+
? spinner
|
|
367
|
+
: '·';
|
|
368
|
+
|
|
369
|
+
final String status = failed
|
|
370
|
+
? 'error'
|
|
371
|
+
: done
|
|
372
|
+
? 'done'
|
|
373
|
+
: active
|
|
374
|
+
? _pct(s.fraction)
|
|
375
|
+
: 'wait';
|
|
376
|
+
|
|
377
|
+
return Padding(
|
|
378
|
+
padding: const EdgeInsets.symmetric(vertical: 3),
|
|
379
|
+
child: Row(
|
|
380
|
+
children: [
|
|
381
|
+
SizedBox(
|
|
382
|
+
width: 18,
|
|
383
|
+
child: Text(glyph,
|
|
384
|
+
style: TextStyle(
|
|
385
|
+
color: color, fontFamily: _mono, fontSize: 14)),
|
|
386
|
+
),
|
|
387
|
+
const SizedBox(width: 8),
|
|
388
|
+
Expanded(
|
|
389
|
+
child: Text(
|
|
390
|
+
_stepLabels[i],
|
|
391
|
+
style: TextStyle(
|
|
392
|
+
color: pending ? _C.faint : _C.text,
|
|
393
|
+
fontFamily: _mono,
|
|
394
|
+
fontSize: 13,
|
|
395
|
+
fontWeight: active ? FontWeight.w600 : FontWeight.w400,
|
|
396
|
+
),
|
|
397
|
+
),
|
|
398
|
+
),
|
|
399
|
+
Text(
|
|
400
|
+
status,
|
|
401
|
+
style: TextStyle(
|
|
402
|
+
color: color,
|
|
403
|
+
fontFamily: _mono,
|
|
404
|
+
fontSize: 12,
|
|
405
|
+
fontFeatures: const [FontFeature.tabularFigures()],
|
|
406
|
+
),
|
|
407
|
+
),
|
|
408
|
+
],
|
|
409
|
+
),
|
|
410
|
+
);
|
|
411
|
+
}),
|
|
412
|
+
),
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ── PROGRESS ──────────────────────────────────────────────────────────
|
|
417
|
+
Widget _progressBlock(OtaOverlayState s) {
|
|
418
|
+
const total = 34;
|
|
419
|
+
final frac = (s.fraction ?? 0).clamp(0.0, 1.0);
|
|
420
|
+
final filled = (frac * total).round();
|
|
421
|
+
final bar = '${'█' * filled}${'░' * (total - filled)}';
|
|
422
|
+
|
|
423
|
+
final speed = (s.bytesPerSec != null && s.bytesPerSec! > 0)
|
|
424
|
+
? '${(s.bytesPerSec! / 1048576).toStringAsFixed(1)} MB/s'
|
|
425
|
+
: '—';
|
|
426
|
+
String eta = '—';
|
|
427
|
+
if (s.bytesPerSec != null &&
|
|
428
|
+
s.bytesPerSec! > 0 &&
|
|
429
|
+
s.totalBytes > s.bytesReceived) {
|
|
430
|
+
final secs = ((s.totalBytes - s.bytesReceived) / s.bytesPerSec!).ceil();
|
|
431
|
+
eta = '${secs}s';
|
|
432
|
+
}
|
|
433
|
+
final phase = s.hasError
|
|
434
|
+
? 'halted'
|
|
435
|
+
: (s.phase?.name ?? 'preparing');
|
|
436
|
+
|
|
437
|
+
return _panelBox(
|
|
438
|
+
child: Column(
|
|
439
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
440
|
+
children: [
|
|
441
|
+
RichText(
|
|
442
|
+
text: TextSpan(
|
|
443
|
+
style: const TextStyle(fontFamily: _mono, fontSize: 13),
|
|
444
|
+
children: [
|
|
445
|
+
TextSpan(
|
|
446
|
+
text: bar,
|
|
447
|
+
style: TextStyle(color: s.hasError ? _C.red : _C.blue)),
|
|
448
|
+
TextSpan(
|
|
449
|
+
text: ' ${_pct(s.fraction)}',
|
|
450
|
+
style: const TextStyle(
|
|
451
|
+
color: _C.text, fontWeight: FontWeight.w700)),
|
|
452
|
+
],
|
|
453
|
+
),
|
|
454
|
+
),
|
|
455
|
+
const SizedBox(height: 10),
|
|
456
|
+
Row(
|
|
457
|
+
children: [
|
|
458
|
+
_stat('speed', speed),
|
|
459
|
+
_stat('eta', eta),
|
|
460
|
+
_stat('phase', phase),
|
|
461
|
+
],
|
|
462
|
+
),
|
|
463
|
+
],
|
|
464
|
+
),
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ── LOG ───────────────────────────────────────────────────────────────
|
|
469
|
+
Widget _logBlock(OtaOverlayState s) {
|
|
470
|
+
return Container(
|
|
471
|
+
height: 132,
|
|
472
|
+
padding: const EdgeInsets.all(12),
|
|
473
|
+
decoration: BoxDecoration(
|
|
474
|
+
color: _C.logBg,
|
|
475
|
+
borderRadius: BorderRadius.circular(8),
|
|
476
|
+
border: Border.all(color: _C.sep),
|
|
477
|
+
),
|
|
478
|
+
child: _TerminalLog(logs: s.logs),
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ── Footer ────────────────────────────────────────────────────────────
|
|
483
|
+
Widget _footer(OtaOverlayState s) {
|
|
484
|
+
final showRetry = s.hasError && (widget.onRetry != null || s.canRetry);
|
|
485
|
+
return Container(
|
|
486
|
+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
487
|
+
decoration: const BoxDecoration(
|
|
488
|
+
color: _C.head,
|
|
489
|
+
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
|
|
490
|
+
border: Border(top: BorderSide(color: _C.border)),
|
|
491
|
+
),
|
|
492
|
+
child: s.hasError
|
|
493
|
+
? Row(
|
|
494
|
+
children: [
|
|
495
|
+
Expanded(
|
|
496
|
+
child: Text(
|
|
497
|
+
s.errorHint ?? 'Close the app and reopen to retry.',
|
|
498
|
+
style: const TextStyle(
|
|
499
|
+
color: _C.dim, fontFamily: _mono, fontSize: 12),
|
|
500
|
+
),
|
|
501
|
+
),
|
|
502
|
+
if (showRetry)
|
|
503
|
+
GestureDetector(
|
|
504
|
+
onTap: widget.onRetry,
|
|
505
|
+
child: Container(
|
|
506
|
+
padding: const EdgeInsets.symmetric(
|
|
507
|
+
horizontal: 14, vertical: 7),
|
|
508
|
+
decoration: BoxDecoration(
|
|
509
|
+
borderRadius: BorderRadius.circular(7),
|
|
510
|
+
border: Border.all(color: _C.border),
|
|
511
|
+
),
|
|
512
|
+
child: const Text('[ retry ]',
|
|
513
|
+
style: TextStyle(
|
|
514
|
+
color: _C.blue,
|
|
515
|
+
fontFamily: _mono,
|
|
516
|
+
fontSize: 13)),
|
|
517
|
+
),
|
|
518
|
+
),
|
|
519
|
+
],
|
|
520
|
+
)
|
|
521
|
+
: Row(
|
|
522
|
+
children: [
|
|
523
|
+
const Text('›',
|
|
524
|
+
style: TextStyle(
|
|
525
|
+
color: _C.green, fontFamily: _mono, fontSize: 13)),
|
|
526
|
+
const SizedBox(width: 8),
|
|
527
|
+
Expanded(
|
|
528
|
+
child: Text(
|
|
529
|
+
(s.message != null && s.message!.isNotEmpty)
|
|
530
|
+
? s.message!
|
|
531
|
+
: 'Applying update, please keep the app open…',
|
|
532
|
+
style: const TextStyle(
|
|
533
|
+
color: _C.dim, fontFamily: _mono, fontSize: 12),
|
|
534
|
+
),
|
|
535
|
+
),
|
|
536
|
+
],
|
|
537
|
+
),
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ── helpers ─────────────────────────────────────────────────────────
|
|
542
|
+
Widget _sectionLabel(String t) => Row(
|
|
543
|
+
children: [
|
|
544
|
+
Text(t,
|
|
545
|
+
style: const TextStyle(
|
|
546
|
+
color: _C.dim,
|
|
547
|
+
fontFamily: _mono,
|
|
548
|
+
fontSize: 11,
|
|
549
|
+
fontWeight: FontWeight.w700,
|
|
550
|
+
letterSpacing: 1.5,
|
|
551
|
+
)),
|
|
552
|
+
const SizedBox(width: 10),
|
|
553
|
+
const Expanded(child: Divider(color: _C.sep, height: 1)),
|
|
554
|
+
],
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
Widget _panelBox({required Widget child}) => Container(
|
|
558
|
+
width: double.infinity,
|
|
559
|
+
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
|
560
|
+
decoration: BoxDecoration(
|
|
561
|
+
color: _C.inner,
|
|
562
|
+
borderRadius: BorderRadius.circular(8),
|
|
563
|
+
border: Border.all(color: _C.sep),
|
|
564
|
+
),
|
|
565
|
+
child: child,
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
Widget _stat(String k, String v) => Expanded(
|
|
569
|
+
child: Column(
|
|
570
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
571
|
+
children: [
|
|
572
|
+
Text(k,
|
|
573
|
+
style: const TextStyle(
|
|
574
|
+
color: _C.faint, fontFamily: _mono, fontSize: 10)),
|
|
575
|
+
const SizedBox(height: 2),
|
|
576
|
+
Text(v,
|
|
577
|
+
overflow: TextOverflow.ellipsis,
|
|
578
|
+
style: const TextStyle(
|
|
579
|
+
color: _C.text, fontFamily: _mono, fontSize: 12)),
|
|
580
|
+
],
|
|
581
|
+
),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
Widget _dot(Color c) => Container(
|
|
585
|
+
width: 11,
|
|
586
|
+
height: 11,
|
|
587
|
+
decoration: BoxDecoration(color: c, shape: BoxShape.circle),
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
Widget _pill(String t, Color c) => Container(
|
|
591
|
+
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 3),
|
|
592
|
+
decoration: BoxDecoration(
|
|
593
|
+
color: c.withValues(alpha: 0.15),
|
|
594
|
+
borderRadius: BorderRadius.circular(6),
|
|
595
|
+
border: Border.all(color: c.withValues(alpha: 0.5)),
|
|
596
|
+
),
|
|
597
|
+
child: Text(t,
|
|
598
|
+
style: TextStyle(
|
|
599
|
+
color: c,
|
|
600
|
+
fontFamily: _mono,
|
|
601
|
+
fontSize: 10,
|
|
602
|
+
fontWeight: FontWeight.w700,
|
|
603
|
+
letterSpacing: 0.5)),
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/// Scrollable terminal log view — auto-scrolls to bottom on new lines.
|
|
608
|
+
///
|
|
609
|
+
/// A line whose text has a leading token followed by 2+ spaces is rendered
|
|
610
|
+
/// with the token in its tag color and the remainder in the body color.
|
|
611
|
+
class _TerminalLog extends StatefulWidget {
|
|
612
|
+
final List<LogLine> logs;
|
|
613
|
+
|
|
614
|
+
const _TerminalLog({required this.logs});
|
|
615
|
+
|
|
616
|
+
@override
|
|
617
|
+
State<_TerminalLog> createState() => _TerminalLogState();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
class _TerminalLogState extends State<_TerminalLog> {
|
|
621
|
+
final _controller = ScrollController();
|
|
622
|
+
|
|
623
|
+
@override
|
|
624
|
+
void didUpdateWidget(covariant _TerminalLog oldWidget) {
|
|
625
|
+
super.didUpdateWidget(oldWidget);
|
|
626
|
+
if (widget.logs.length > oldWidget.logs.length) {
|
|
627
|
+
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
628
|
+
if (_controller.hasClients) {
|
|
629
|
+
_controller.animateTo(
|
|
630
|
+
_controller.position.maxScrollExtent,
|
|
631
|
+
duration: const Duration(milliseconds: 200),
|
|
632
|
+
curve: Curves.easeOut,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
@override
|
|
640
|
+
void dispose() {
|
|
641
|
+
_controller.dispose();
|
|
642
|
+
super.dispose();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
@override
|
|
646
|
+
Widget build(BuildContext context) {
|
|
647
|
+
final logs = widget.logs.isEmpty
|
|
648
|
+
? const [LogLine('gray', 'status waiting for events…')]
|
|
649
|
+
: widget.logs;
|
|
650
|
+
return ListView.builder(
|
|
651
|
+
controller: _controller,
|
|
652
|
+
itemCount: logs.length,
|
|
653
|
+
itemBuilder: (context, i) {
|
|
654
|
+
final line = logs[i];
|
|
655
|
+
final color = _logColors[line.color] ?? _C.dim;
|
|
656
|
+
|
|
657
|
+
// Split "tag rest" (2+ spaces) into an accent tag + body.
|
|
658
|
+
final match = RegExp(r'^(\S+)(\s{2,})(.*)$').firstMatch(line.text);
|
|
659
|
+
final Widget content;
|
|
660
|
+
if (match != null) {
|
|
661
|
+
content = RichText(
|
|
662
|
+
text: TextSpan(
|
|
663
|
+
style: const TextStyle(fontFamily: _mono, fontSize: 12),
|
|
664
|
+
children: [
|
|
665
|
+
TextSpan(
|
|
666
|
+
text: '${match.group(1)!.padRight(8)} ',
|
|
667
|
+
style: TextStyle(color: color)),
|
|
668
|
+
TextSpan(
|
|
669
|
+
text: match.group(3),
|
|
670
|
+
style: const TextStyle(color: _C.text)),
|
|
671
|
+
],
|
|
672
|
+
),
|
|
673
|
+
);
|
|
674
|
+
} else {
|
|
675
|
+
content = Text(
|
|
676
|
+
line.text,
|
|
677
|
+
style: TextStyle(
|
|
678
|
+
fontFamily: _mono, fontSize: 12, color: color),
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return Padding(
|
|
683
|
+
padding: const EdgeInsets.only(bottom: 3),
|
|
684
|
+
child: content,
|
|
685
|
+
);
|
|
686
|
+
},
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/// Drives the forced-update progress overlay from SDK code.
|
|
692
|
+
class OtaOverlayManager {
|
|
693
|
+
OtaOverlayManager._();
|
|
694
|
+
|
|
695
|
+
static final OtaOverlayManager instance = OtaOverlayManager._();
|
|
696
|
+
|
|
697
|
+
OverlayState? _overlayState;
|
|
698
|
+
final ValueNotifier<OtaOverlayState> _state = ValueNotifier(
|
|
699
|
+
const OtaOverlayState(),
|
|
700
|
+
);
|
|
701
|
+
OverlayEntry? _entry;
|
|
702
|
+
bool _disposed = false;
|
|
703
|
+
DateTime? _startTime;
|
|
704
|
+
|
|
705
|
+
// Download-speed tracking.
|
|
706
|
+
int _lastBytes = 0;
|
|
707
|
+
DateTime? _lastBytesAt;
|
|
708
|
+
|
|
709
|
+
OverlayState? Function()? _resolver;
|
|
710
|
+
|
|
711
|
+
/// Minimum time the overlay stays visible (7 seconds).
|
|
712
|
+
static const Duration _minDuration = Duration(seconds: 7);
|
|
713
|
+
|
|
714
|
+
/// Dwell time after patch is applied before restart.
|
|
715
|
+
static const Duration _successDwell = Duration(seconds: 2);
|
|
716
|
+
|
|
717
|
+
static const Duration errorDwell = Duration(seconds: 8);
|
|
718
|
+
|
|
719
|
+
void setResolver(OverlayState? Function()? resolver) => _resolver = resolver;
|
|
720
|
+
|
|
721
|
+
void register(OverlayState state) => _overlayState = state;
|
|
722
|
+
|
|
723
|
+
void unregister(OverlayState state) {
|
|
724
|
+
if (_overlayState == state) _overlayState = null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
OtaOverlayHandle? begin({
|
|
728
|
+
String? message,
|
|
729
|
+
String? commitMessage,
|
|
730
|
+
String? targetVersion,
|
|
731
|
+
String? currentVersion,
|
|
732
|
+
String? errorHint,
|
|
733
|
+
String? bundleHash,
|
|
734
|
+
String? gitCommit,
|
|
735
|
+
String? channel,
|
|
736
|
+
String? platform,
|
|
737
|
+
}) {
|
|
738
|
+
if (_disposed) return null;
|
|
739
|
+
final overlay = _overlayState ?? _resolver?.call();
|
|
740
|
+
if (overlay == null) return null;
|
|
741
|
+
|
|
742
|
+
_startTime = DateTime.now();
|
|
743
|
+
_lastBytes = 0;
|
|
744
|
+
_lastBytesAt = null;
|
|
745
|
+
|
|
746
|
+
final logs = <LogLine>[
|
|
747
|
+
if (channel != null) LogLine('gray', 'channel $channel'),
|
|
748
|
+
if (platform != null) LogLine('gray', 'platform $platform'),
|
|
749
|
+
if (bundleHash != null) LogLine('gray', 'bundle $bundleHash'),
|
|
750
|
+
if (gitCommit != null) LogLine('gray', 'commit $gitCommit'),
|
|
751
|
+
if (commitMessage != null && commitMessage.isNotEmpty)
|
|
752
|
+
LogLine('cyan', 'msg $commitMessage'),
|
|
753
|
+
if (message != null && message.isNotEmpty)
|
|
754
|
+
LogLine('white', 'deploy $message'),
|
|
755
|
+
];
|
|
756
|
+
|
|
757
|
+
_state.value = OtaOverlayState(
|
|
758
|
+
message: message,
|
|
759
|
+
commitMessage: commitMessage,
|
|
760
|
+
targetVersion: targetVersion,
|
|
761
|
+
currentVersion: currentVersion,
|
|
762
|
+
errorHint: errorHint,
|
|
763
|
+
bundleHash: bundleHash,
|
|
764
|
+
gitCommit: gitCommit,
|
|
765
|
+
channel: channel,
|
|
766
|
+
platform: platform,
|
|
767
|
+
logs: logs,
|
|
768
|
+
activeStep: 0,
|
|
769
|
+
);
|
|
770
|
+
_entry = OverlayEntry(builder: (_) => OtaProgressOverlay(state: _state));
|
|
771
|
+
overlay.insert(_entry!);
|
|
772
|
+
return OtaOverlayHandle._(this);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
void _log(String color, String text) {
|
|
776
|
+
if (_disposed) return;
|
|
777
|
+
final current = _state.value;
|
|
778
|
+
_state.value = current.copyWith(
|
|
779
|
+
logs: [...current.logs, LogLine(color, text)],
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/// Map phase to step index.
|
|
784
|
+
int _phaseToStep(PatchApplyPhase? phase) {
|
|
785
|
+
switch (phase) {
|
|
786
|
+
case PatchApplyPhase.downloading:
|
|
787
|
+
return 1;
|
|
788
|
+
case PatchApplyPhase.verifying:
|
|
789
|
+
return 2;
|
|
790
|
+
case PatchApplyPhase.finalizing:
|
|
791
|
+
return 3;
|
|
792
|
+
case null:
|
|
793
|
+
return 0;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
void _update(PatchApplyProgress progress) {
|
|
798
|
+
if (_disposed || _entry == null) return;
|
|
799
|
+
final s = _state.value;
|
|
800
|
+
final pct = progress.fraction;
|
|
801
|
+
final pctText = pct != null
|
|
802
|
+
? '${(pct.clamp(0.0, 1.0) * 100).toStringAsFixed(0)}%'
|
|
803
|
+
: '';
|
|
804
|
+
|
|
805
|
+
final newStep = _phaseToStep(progress.phase);
|
|
806
|
+
|
|
807
|
+
// Compute download speed from byte deltas.
|
|
808
|
+
int? bytesPerSec = s.bytesPerSec;
|
|
809
|
+
if (progress.phase == PatchApplyPhase.downloading &&
|
|
810
|
+
progress.bytesReceived > 0) {
|
|
811
|
+
final now = DateTime.now();
|
|
812
|
+
if (_lastBytesAt != null && progress.bytesReceived > _lastBytes) {
|
|
813
|
+
final dt = now.difference(_lastBytesAt!).inMilliseconds / 1000.0;
|
|
814
|
+
if (dt > 0) {
|
|
815
|
+
bytesPerSec = ((progress.bytesReceived - _lastBytes) / dt).round();
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
_lastBytes = progress.bytesReceived;
|
|
819
|
+
_lastBytesAt = now;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Log phase transitions with staggered delays.
|
|
823
|
+
if (s.phase != progress.phase) {
|
|
824
|
+
switch (progress.phase) {
|
|
825
|
+
case PatchApplyPhase.downloading:
|
|
826
|
+
_log('gray', 'status connecting…');
|
|
827
|
+
_delayedLog(300, 'gray', 'status fetching metadata…');
|
|
828
|
+
_delayedLog(700, 'cyan', 'resolve download url');
|
|
829
|
+
_delayedLog(1100, 'gray', 'status downloading…');
|
|
830
|
+
break;
|
|
831
|
+
case PatchApplyPhase.verifying:
|
|
832
|
+
_log('cyan', 'verify checking hash…');
|
|
833
|
+
_delayedLog(400, 'cyan', 'verify checking signature…');
|
|
834
|
+
_delayedLog(800, 'success', 'verify passed ✓');
|
|
835
|
+
break;
|
|
836
|
+
case PatchApplyPhase.finalizing:
|
|
837
|
+
_log('gray', 'status staging files…');
|
|
838
|
+
_delayedLog(500, 'gray', 'status installing…');
|
|
839
|
+
_delayedLog(1000, 'gray', 'status finalizing…');
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Log download progress with size.
|
|
845
|
+
if (progress.phase == PatchApplyPhase.downloading &&
|
|
846
|
+
pctText.isNotEmpty &&
|
|
847
|
+
progress.totalBytes > 0) {
|
|
848
|
+
final sizeMB = (progress.totalBytes / 1048576).toStringAsFixed(1);
|
|
849
|
+
_log('gray', 'download ${sizeMB}MB $pctText');
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
_state.value = _state.value.copyWith(
|
|
853
|
+
phase: progress.phase,
|
|
854
|
+
fraction: progress.fraction,
|
|
855
|
+
bytesReceived: progress.bytesReceived,
|
|
856
|
+
totalBytes: progress.totalBytes > 0 ? progress.totalBytes : null,
|
|
857
|
+
bytesPerSec: bytesPerSec,
|
|
858
|
+
activeStep: newStep,
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/// Log with a delay for staged feel.
|
|
863
|
+
void _delayedLog(int ms, String color, String text) {
|
|
864
|
+
Future.delayed(Duration(milliseconds: ms), () {
|
|
865
|
+
if (_disposed) return;
|
|
866
|
+
_log(color, text);
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
void _end({bool hasError = false, String? errorText}) {
|
|
871
|
+
if (_disposed || _entry == null) return;
|
|
872
|
+
if (hasError) {
|
|
873
|
+
_log('red', 'error ${errorText ?? 'unknown'}');
|
|
874
|
+
_log('yellow', 'action rolling back to base apk');
|
|
875
|
+
} else {
|
|
876
|
+
_log('success', 'done patch installed ✓');
|
|
877
|
+
_delayedLog(300, 'cyan', 'status restarting…');
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Calculate remaining time to enforce minimum duration.
|
|
881
|
+
final elapsed = _startTime != null
|
|
882
|
+
? DateTime.now().difference(_startTime!)
|
|
883
|
+
: _minDuration;
|
|
884
|
+
final remaining = _minDuration - elapsed;
|
|
885
|
+
final delay = remaining.isNegative ? Duration.zero : remaining;
|
|
886
|
+
|
|
887
|
+
_state.value = _state.value.copyWith(
|
|
888
|
+
hasError: hasError,
|
|
889
|
+
errorText: errorText,
|
|
890
|
+
canRetry: hasError,
|
|
891
|
+
activeStep: hasError ? _state.value.activeStep : 4,
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
final entry = _entry;
|
|
895
|
+
final dwell = hasError ? errorDwell : _successDwell + delay;
|
|
896
|
+
Future.delayed(dwell, () {
|
|
897
|
+
if (_disposed) return;
|
|
898
|
+
entry?.remove();
|
|
899
|
+
if (identical(_entry, entry)) _entry = null;
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/// Shows a toast notification on the forced-update overlay when a rollback
|
|
904
|
+
/// occurs.
|
|
905
|
+
void showRollbackToast({
|
|
906
|
+
required String message,
|
|
907
|
+
required String previousVersion,
|
|
908
|
+
}) {
|
|
909
|
+
if (_disposed) return;
|
|
910
|
+
final overlay = _overlayState ?? _resolver?.call();
|
|
911
|
+
if (overlay == null) return;
|
|
912
|
+
|
|
913
|
+
final toastEntry = OverlayEntry(
|
|
914
|
+
builder: (context) => Positioned(
|
|
915
|
+
bottom: 100,
|
|
916
|
+
left: 24,
|
|
917
|
+
right: 24,
|
|
918
|
+
child: Material(
|
|
919
|
+
color: Colors.transparent,
|
|
920
|
+
child: Container(
|
|
921
|
+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
922
|
+
decoration: BoxDecoration(
|
|
923
|
+
color: Colors.black.withValues(alpha: 0.85),
|
|
924
|
+
borderRadius: BorderRadius.circular(12),
|
|
925
|
+
border: Border.all(color: Colors.orange.withValues(alpha: 0.5)),
|
|
926
|
+
boxShadow: [
|
|
927
|
+
BoxShadow(
|
|
928
|
+
color: Colors.black.withValues(alpha: 0.3),
|
|
929
|
+
blurRadius: 12,
|
|
930
|
+
offset: const Offset(0, 4),
|
|
931
|
+
),
|
|
932
|
+
],
|
|
933
|
+
),
|
|
934
|
+
child: Row(
|
|
935
|
+
mainAxisSize: MainAxisSize.min,
|
|
936
|
+
children: [
|
|
937
|
+
const Icon(Icons.info_outline, color: Colors.orange, size: 20),
|
|
938
|
+
const SizedBox(width: 12),
|
|
939
|
+
Flexible(
|
|
940
|
+
child: Column(
|
|
941
|
+
mainAxisSize: MainAxisSize.min,
|
|
942
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
943
|
+
children: [
|
|
944
|
+
Text(
|
|
945
|
+
message,
|
|
946
|
+
style: const TextStyle(
|
|
947
|
+
color: Colors.white,
|
|
948
|
+
fontSize: 14,
|
|
949
|
+
fontWeight: FontWeight.w500,
|
|
950
|
+
),
|
|
951
|
+
),
|
|
952
|
+
Text(
|
|
953
|
+
'Restored: $previousVersion',
|
|
954
|
+
style: TextStyle(
|
|
955
|
+
color: Colors.grey[400],
|
|
956
|
+
fontSize: 12,
|
|
957
|
+
),
|
|
958
|
+
),
|
|
959
|
+
],
|
|
960
|
+
),
|
|
961
|
+
),
|
|
962
|
+
],
|
|
963
|
+
),
|
|
964
|
+
),
|
|
965
|
+
),
|
|
966
|
+
),
|
|
967
|
+
);
|
|
968
|
+
|
|
969
|
+
overlay.insert(toastEntry);
|
|
970
|
+
Future.delayed(const Duration(seconds: 4), () {
|
|
971
|
+
if (!_disposed) toastEntry.remove();
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/// Handle returned by [OtaOverlayManager.begin] to update / dismiss the
|
|
977
|
+
/// overlay.
|
|
978
|
+
class OtaOverlayHandle {
|
|
979
|
+
OtaOverlayHandle._(this._manager);
|
|
980
|
+
|
|
981
|
+
final OtaOverlayManager _manager;
|
|
982
|
+
|
|
983
|
+
void update(PatchApplyProgress progress) => _manager._update(progress);
|
|
984
|
+
|
|
985
|
+
void end({bool hasError = false, String? errorText}) =>
|
|
986
|
+
_manager._end(hasError: hasError, errorText: errorText);
|
|
987
|
+
}
|