diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 9a19d4872e..ea349e09be 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -58,5 +58,5 @@ Generic Flutter and Dart standards live in `.claude/rules/`: - Run Flutter commands from `mobile/`. - If dependencies change, run `flutter pub get`. -- If you touch generated-code inputs such as Riverpod, Freezed, JSON, Mockito, or Drift, run `dart run build_runner build --delete-conflicting-outputs` and commit the generated files. +- If you touch generated-code inputs such as Riverpod, JSON, Mockito, or Drift, run `dart run build_runner build --delete-conflicting-outputs` and commit the generated files. - Add or update tests with the change. For UI changes, update goldens when appropriate. diff --git a/.claude/hooks/pre-commit-build-runner.sh b/.claude/hooks/pre-commit-build-runner.sh index 15106380ef..0e8d8f498f 100755 --- a/.claude/hooks/pre-commit-build-runner.sh +++ b/.claude/hooks/pre-commit-build-runner.sh @@ -2,7 +2,7 @@ # Git pre-commit hook component # Runs build_runner if any staged Dart files contain code generation annotations # -# Annotations: @freezed, @riverpod, @Riverpod, @JsonSerializable, +# Annotations: @riverpod, @Riverpod, @JsonSerializable, # @GenerateMocks, @HiveType, @DriftDatabase, @DriftAccessor set -e @@ -15,7 +15,7 @@ FIRST_CMD=$(echo "$COMMAND" | head -1 | sed 's/\s*&&.*//' | sed 's/\s*|.*//' | s echo "$FIRST_CMD" | grep -qE '^\s*git\s+commit\b' || exit 0 # Get staged Dart files (excluding generated files) -STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.dart$' | grep -v '\.g\.dart$' | grep -v '\.freezed\.dart$' || true) +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.dart$' | grep -v '\.g\.dart$' || true) if [ -z "$STAGED_FILES" ]; then exit 0 @@ -29,7 +29,7 @@ for FILE in $STAGED_FILES; do [ -f "$FILE" ] || continue # Check if file contains code generation annotations - if grep -qE '@(freezed|riverpod|Riverpod|JsonSerializable|GenerateMocks|HiveType|DriftDatabase|DriftAccessor)' "$FILE"; then + if grep -qE '@(riverpod|Riverpod|JsonSerializable|GenerateMocks|HiveType|DriftDatabase|DriftAccessor)' "$FILE"; then # Find the package root PACKAGE_DIR="$FILE" while [ "$PACKAGE_DIR" != "." ] && [ "$PACKAGE_DIR" != "/" ]; do @@ -60,9 +60,7 @@ if [ -n "$PACKAGE_ROOTS" ]; then # Stage any regenerated files for FILE in $STAGED_FILES; do GENERATED="${FILE%.dart}.g.dart" - FREEZED="${FILE%.dart}.freezed.dart" [ -f "$GENERATED" ] && git add "$GENERATED" - [ -f "$FREEZED" ] && git add "$FREEZED" done fi diff --git a/.claude/rules/agent_workflow.md b/.claude/rules/agent_workflow.md index 25861e06c8..cf6e4f057c 100644 --- a/.claude/rules/agent_workflow.md +++ b/.claude/rules/agent_workflow.md @@ -320,7 +320,7 @@ wrong. The real explanations, in order of frequency: 1. The change broke the behavior the test asserts (most common). 2. The change broke a test setup invariant — mocks, fixtures, or - generated files (Riverpod, Freezed, JSON, Mockito, Drift) out of + generated files (Riverpod, JSON, Mockito, Drift) out of sync with their inputs. 3. The change reveals a real race or order-dependence the test correctly catches. diff --git a/.claude/rules/ios_build_troubleshooting.md b/.claude/rules/ios_build_troubleshooting.md index 64db3d08e7..7ab710f25f 100644 --- a/.claude/rules/ios_build_troubleshooting.md +++ b/.claude/rules/ios_build_troubleshooting.md @@ -30,13 +30,13 @@ flutter clean && flutter pub get && cd ios && pod install Do not create PRs to change `pubspec.lock` or `Package.resolved` for the stale-cache case. -## Cause 3: the generated Swift package floor is stuck at iOS 13 +## Cause 3: the generated Swift package floor is below iOS 16 -Xcode reports, for `FlutterGeneratedPluginSwiftPackage`, that `firebase-*` require iOS 15.0 and `c2pa-flutter` / `divine-video-player` require 16.0 "but this target supports 13". The project is not misconfigured: every `IPHONEOS_DEPLOYMENT_TARGET` in `mobile/ios/Runner.xcodeproj/project.pbxproj` and the Podfile `platform` are 16.0. The floor that fails is the `platforms:` line of the generated, gitignored `mobile/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift`. +Xcode reports, for `FlutterGeneratedPluginSwiftPackage`, that `c2pa-flutter` / `divine-video-player` require iOS 16.0 but the generated package supports an older target. The project is not misconfigured: every `IPHONEOS_DEPLOYMENT_TARGET` in `mobile/ios/Runner.xcodeproj/project.pbxproj` and the Podfile `platform` are 16.0. The floor that fails is the `platforms:` line of the generated, gitignored `mobile/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift`. -Flutter (3.44) writes that manifest at its default `.iOS("13.0")` every time it injects plugins — `flutter pub get`, `flutter analyze`, `flutter test`, and the implicit pub step of most other commands. `flutter build ios` and `flutter run` normally raise it to the project's deployment target afterwards, but that step silently does nothing when `xcodebuild -showBuildSettings` exceeds Flutter's 60-second timeout (a cold machine or a fresh worktree). +Flutter writes that manifest at its own default every time it injects plugins — 3.44 used `.iOS("13.0")`, while 3.47 uses `.iOS("15.0")`. Commands such as `flutter pub get`, `flutter analyze`, `flutter test`, and the implicit pub step of most other commands can therefore lower it. `flutter build ios` and `flutter run` normally raise it to the project's deployment target afterwards, but that step can silently do nothing when `xcodebuild -showBuildSettings` exceeds Flutter's 60-second timeout (a cold machine or a fresh worktree). -The shared Runner scheme's pre-action repairs the generated manifest in place, without invoking Flutter. **That repair cannot rescue the build it runs in.** Xcode emits `Resolve Package Graph` at the very start of a build, before it runs scheme pre-actions, so a build that starts on a 13.0 floor still fails with all seven errors; the repair then lands, and the *next* build succeeds. Measured 2026-09-06 on a device Profile build: sabotaged floor → `** BUILD FAILED **` with the repair line present in the same log → unchanged rebuild → `** BUILD SUCCEEDED **`. +The shared Runner scheme's pre-action uses `scripts/ensure_ios_swift_package_floor.rb` to repair any generated target below iOS 16 in place, without invoking Flutter. Codemagic uses the same helper. **That repair cannot rescue the Xcode build it runs in.** Xcode emits `Resolve Package Graph` at the very start of a build, before it runs scheme pre-actions, so a build that starts below 16 still fails; the repair then lands, and the *next* build succeeds. Measured 2026-09-06 on a device Profile build: sabotaged floor → `** BUILD FAILED **` with the repair line present in the same log → unchanged rebuild → `** BUILD SUCCEEDED **`. So the failure is self-limiting rather than prevented: if Xcode fails this way, just build again. To skip the wasted first build after any `flutter pub get`, `flutter analyze` or `flutter test`, run this from `mobile/` before returning to Xcode: @@ -45,6 +45,6 @@ flutter build ios --config-only grep -A1 "platforms:" ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift ``` -The grep must show `16.0`. If it still shows `13.0`, run the build command again — Flutter skips its own bump when `xcodebuild -showBuildSettings` exceeds its 60-second timeout on a cold machine. +The grep must show `16.0` or newer. If it still shows an older version, run the build command again — Flutter can skip its own bump when `xcodebuild -showBuildSettings` exceeds its 60-second timeout on a cold machine. Do not add a Flutter command to `mobile/pre_build_ios.sh`: it runs as a pre-action of the shared Runner scheme on every Xcode build, so a `flutter pub get` there re-lowered the floor mid-build and made every Xcode build and Profile run fail this way. The script now repairs the manifest directly and syncs CocoaPods. CI performs the same repair separately in `codemagic.yaml` before archiving. diff --git a/.claude/rules/self_review_checklist.md b/.claude/rules/self_review_checklist.md index a7a08c0d2e..2134970cd9 100644 --- a/.claude/rules/self_review_checklist.md +++ b/.claude/rules/self_review_checklist.md @@ -266,7 +266,7 @@ Then: rather than patch. See [`agent_workflow.md`](agent_workflow.md#5-failing-tests-are-never-acceptable-and-always-your-fault) and [when the failure is not yours](agent_workflow.md#when-the-failure-is-not-yours). -- [ ] Generated files (Riverpod, Freezed, JSON, Mockito, Drift) are +- [ ] Generated files (Riverpod, JSON, Mockito, Drift) are regenerated and staged if you touched inputs. - [ ] `pubspec.lock` churn from a different SDK/pub-resolver run is **discarded**, not committed — only commit lockfile changes that come diff --git a/.codex/hooks/pre-commit-build-runner.sh b/.codex/hooks/pre-commit-build-runner.sh index 4a4e50e381..dc4f8bb556 100755 --- a/.codex/hooks/pre-commit-build-runner.sh +++ b/.codex/hooks/pre-commit-build-runner.sh @@ -2,7 +2,7 @@ # Git pre-commit hook component # Runs build_runner if any staged Dart files contain code generation annotations # -# Annotations: @freezed, @riverpod, @Riverpod, @JsonSerializable, +# Annotations: @riverpod, @Riverpod, @JsonSerializable, # @GenerateMocks, @HiveType, @DriftDatabase, @DriftAccessor set -e @@ -35,7 +35,7 @@ PACKAGE_ROOTS=() while IFS= read -r -d '' FILE; do case "$FILE" in - *.g.dart|*.freezed.dart|*.mocks.dart) + *.g.dart|*.mocks.dart) continue ;; *.dart) @@ -48,7 +48,7 @@ while IFS= read -r -d '' FILE; do STAGED_FILES[${#STAGED_FILES[@]}]="$FILE" [ -f "$FILE" ] || continue - if grep -qE '@(freezed|riverpod|Riverpod|JsonSerializable|GenerateMocks|HiveType|DriftDatabase|DriftAccessor)' "$FILE"; then + if grep -qE '@(riverpod|Riverpod|JsonSerializable|GenerateMocks|HiveType|DriftDatabase|DriftAccessor)' "$FILE"; then PACKAGE_DIR=$(dirname "$FILE") while [ "$PACKAGE_DIR" != "." ] && [ "$PACKAGE_DIR" != "/" ]; do if [ -f "$PACKAGE_DIR/pubspec.yaml" ]; then @@ -87,14 +87,10 @@ if [ ${#PACKAGE_ROOTS[@]} -gt 0 ]; then # Stage any regenerated files for FILE in "${STAGED_FILES[@]}"; do GENERATED="${FILE%.dart}.g.dart" - FREEZED="${FILE%.dart}.freezed.dart" MOCKS="${FILE%.dart}.mocks.dart" if [ -f "$GENERATED" ]; then git add "$GENERATED" fi - if [ -f "$FREEZED" ]; then - git add "$FREEZED" - fi if [ -f "$MOCKS" ]; then git add "$MOCKS" fi diff --git a/.codex/scripts/test-config.sh b/.codex/scripts/test-config.sh index 7ab80c5ef5..58204c2e8e 100755 --- a/.codex/scripts/test-config.sh +++ b/.codex/scripts/test-config.sh @@ -284,7 +284,7 @@ cat > "$TEST_REPO/mobile/lib/info only.dart" <<'EOF' void main() {} EOF cat > "$TEST_REPO/mobile/lib/with space.dart" <<'EOF' -@freezed +@JsonSerializable() class GeneratedInput {} EOF diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3b61ed0336..c972112eb1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -44,7 +44,7 @@ updates: # constrained by `dependency_overrides`. mobile/pubspec.yaml has exactly that # shape, so the ecosystem could silently produce nothing at all. # • dependabot/dependabot-core#15654 — unmerged fix for Dependabot rewriting the -# lockfile's Flutter constraint (`3.44.0` -> `>=3.44.0`), which would dirty +# lockfile's Flutter constraint (`3.47.0` -> `>=3.47.0`), which would dirty # mobile/pubspec.lock on every run and fight the Generated Files check. # • dependabot/dependabot-core#11860 — the Flutter checkout that pub updates # depend on fails on `bin/internal/engine.version`. diff --git a/.github/scripts/tests/test_codemagic_shorebird_config.py b/.github/scripts/tests/test_codemagic_shorebird_config.py index cbefa20bd4..6ff57d5950 100644 --- a/.github/scripts/tests/test_codemagic_shorebird_config.py +++ b/.github/scripts/tests/test_codemagic_shorebird_config.py @@ -222,9 +222,9 @@ def test_shorebird_required_defines_reject_empty_values(self) -> None: ) def test_shorebird_install_reuses_only_the_expected_cached_version(self) -> None: - self.assertIn('EXPECTED_SHOREBIRD_VERSION="Shorebird 1.6.117"', self.contents) + self.assertIn('EXPECTED_SHOREBIRD_VERSION="Shorebird 1.6.120"', self.contents) self.assertIn( - 'EXPECTED_SHOREBIRD_REVISION="45facdd4e4b3c39e0d260107977584f0b7c66bec"', + 'EXPECTED_SHOREBIRD_REVISION="5ac7f9a9a5c4a5e66a958e608da0f73e34a3d6bb"', self.contents, ) self.assertIn('if [ -x "$SHOREBIRD_BIN" ]; then', self.contents) @@ -243,6 +243,14 @@ def test_shorebird_cli_install_is_pinned_to_a_full_commit(self) -> None: self.assertNotIn("raw.githubusercontent.com/shorebirdtech/install", self.contents) self.assertNotRegex(self.contents, r"git clone .* (?:stable|v[0-9])") + def test_ios_spm_floor_uses_the_shared_version_aware_repair(self) -> None: + definition = self._definition_block("prepare_ios_spm_packages") + + self.assertIn("ruby scripts/ensure_ios_swift_package_floor.rb", definition) + self.assertIn("FlutterGeneratedPluginSwiftPackage/Package.swift", definition) + self.assertIn("FlutterFramework/Package.swift", definition) + self.assertNotIn('.gsub(\'.iOS("13.0")\'', definition) + def test_shorebird_release_commands_are_signed_and_preflighted(self) -> None: self.assertIn("*preflight_shorebird_ios_release", self.contents) self.assertIn("*preflight_shorebird_android_release", self.contents) diff --git a/.github/scripts/tests/test_ios_swift_package_floor.py b/.github/scripts/tests/test_ios_swift_package_floor.py new file mode 100644 index 0000000000..658878c054 --- /dev/null +++ b/.github/scripts/tests/test_ios_swift_package_floor.py @@ -0,0 +1,75 @@ +import subprocess +import tempfile +import unittest +from pathlib import Path + + +SCRIPT = ( + Path(__file__).resolve().parents[3] + / "mobile" + / "scripts" + / "ensure_ios_swift_package_floor.rb" +) + + +class IosSwiftPackageFloorTest(unittest.TestCase): + def _run(self, declaration: str | None) -> tuple[subprocess.CompletedProcess[str], str]: + with tempfile.TemporaryDirectory() as directory: + manifest = Path(directory) / "Package.swift" + platforms = ( + f" platforms: [\n {declaration}\n ],\n" + if declaration is not None + else "" + ) + manifest.write_text( + "let package = Package(\n" + ' name: "FlutterGeneratedPluginSwiftPackage",\n' + f"{platforms}" + ")\n" + ) + result = subprocess.run( + ["ruby", str(SCRIPT), str(manifest)], + check=False, + text=True, + capture_output=True, + ) + return result, manifest.read_text() + + def test_raises_legacy_and_current_flutter_floors(self) -> None: + for declaration in ( + '.iOS("13.0")', + ".iOS(.v13)", + '.iOS("15.0")', + ".iOS(.v15)", + ): + with self.subTest(declaration=declaration): + result, contents = self._run(declaration) + + self.assertEqual(0, result.returncode, result.stderr) + self.assertIn('.iOS("16.0")', contents) + self.assertNotIn(declaration, contents) + + def test_keeps_supported_and_future_floors(self) -> None: + for declaration in ('.iOS("16.0")', ".iOS(.v16)", '.iOS("17.0")'): + with self.subTest(declaration=declaration): + result, contents = self._run(declaration) + + self.assertEqual(0, result.returncode, result.stderr) + self.assertIn(declaration, contents) + + def test_adds_the_floor_when_platforms_are_absent(self) -> None: + result, contents = self._run(None) + + self.assertEqual(0, result.returncode, result.stderr) + self.assertIn('.iOS("16.0")', contents) + + def test_rejects_a_platforms_block_without_ios(self) -> None: + result, contents = self._run('.macOS("12.0")') + + self.assertNotEqual(0, result.returncode) + self.assertIn("platforms block to declare an iOS target", result.stderr) + self.assertNotIn('.iOS("16.0")', contents) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/analytics.yaml b/.github/workflows/analytics.yaml index f34e4ee8e5..d371007818 100644 --- a/.github/workflows/analytics.yaml +++ b/.github/workflows/analytics.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/analytics" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 73 diff --git a/.github/workflows/app_update_repository.yaml b/.github/workflows/app_update_repository.yaml index 6b02d91392..ff5f8166e0 100644 --- a/.github/workflows/app_update_repository.yaml +++ b/.github/workflows/app_update_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/app_update_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 89 diff --git a/.github/workflows/app_version_client.yaml b/.github/workflows/app_version_client.yaml index 3c0b299862..8dda4e9783 100644 --- a/.github/workflows/app_version_client.yaml +++ b/.github/workflows/app_version_client.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/app_version_client" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 76 diff --git a/.github/workflows/background_uploader.yaml b/.github/workflows/background_uploader.yaml index 9556148038..30dad008c0 100644 --- a/.github/workflows/background_uploader.yaml +++ b/.github/workflows/background_uploader.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/background_uploader" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 69 diff --git a/.github/workflows/badge_repository.yaml b/.github/workflows/badge_repository.yaml index 69cd7fe3e4..ceed1fdccf 100644 --- a/.github/workflows/badge_repository.yaml +++ b/.github/workflows/badge_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/badge_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/blossom_upload_service.yaml b/.github/workflows/blossom_upload_service.yaml index bf81559b36..d7cbaaab76 100644 --- a/.github/workflows/blossom_upload_service.yaml +++ b/.github/workflows/blossom_upload_service.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/blossom_upload_service" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 100 diff --git a/.github/workflows/blurhash_service.yaml b/.github/workflows/blurhash_service.yaml index 99ba96f110..bc075dabd0 100644 --- a/.github/workflows/blurhash_service.yaml +++ b/.github/workflows/blurhash_service.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/blurhash_service" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/bookmarks_repository.yaml b/.github/workflows/bookmarks_repository.yaml index 24c9ccf6ec..8cc4bbbae9 100644 --- a/.github/workflows/bookmarks_repository.yaml +++ b/.github/workflows/bookmarks_repository.yaml @@ -24,7 +24,7 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/bookmarks_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" # Stated rather than omitted: 28 package workflows leave this key out and # the VeryGood default is 100, so an omitted key gates at 100 silently. min_coverage: 100 diff --git a/.github/workflows/cache_sync.yml b/.github/workflows/cache_sync.yml index 4bea10156c..1cec579891 100644 --- a/.github/workflows/cache_sync.yml +++ b/.github/workflows/cache_sync.yml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/cache_sync" - flutter_version: "3.44.9" + flutter_version: "3.47.2" coverage_excludes: "**/*.g.dart" diff --git a/.github/workflows/caption_generator.yaml b/.github/workflows/caption_generator.yaml index 0fe0d71344..23dced62a1 100644 --- a/.github/workflows/caption_generator.yaml +++ b/.github/workflows/caption_generator.yaml @@ -24,7 +24,7 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/caption_generator" - flutter_version: "3.44.9" + flutter_version: "3.47.2" # The Dart workflow above never compiles the native Kotlin, so a compile # error would pass green until the package is wired into an app build. @@ -45,7 +45,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true - name: Setup Gradle diff --git a/.github/workflows/categories_repository.yaml b/.github/workflows/categories_repository.yaml index 362c6b41e8..4309a6aa68 100644 --- a/.github/workflows/categories_repository.yaml +++ b/.github/workflows/categories_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/categories_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/collaborator_repository.yaml b/.github/workflows/collaborator_repository.yaml index 56561ee080..430a34a1e7 100644 --- a/.github/workflows/collaborator_repository.yaml +++ b/.github/workflows/collaborator_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/collaborator_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 96 diff --git a/.github/workflows/comments_repository.yaml b/.github/workflows/comments_repository.yaml index 15ab5cc770..19ea1f1591 100644 --- a/.github/workflows/comments_repository.yaml +++ b/.github/workflows/comments_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/comments_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 80 diff --git a/.github/workflows/content_blocklist_repository.yaml b/.github/workflows/content_blocklist_repository.yaml index 89137ca7c1..6222430e07 100644 --- a/.github/workflows/content_blocklist_repository.yaml +++ b/.github/workflows/content_blocklist_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/content_blocklist_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/content_policy.yaml b/.github/workflows/content_policy.yaml index 44a1fe5f88..8a22aea126 100644 --- a/.github/workflows/content_policy.yaml +++ b/.github/workflows/content_policy.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/content_policy" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/coordinator_route_probe.yml b/.github/workflows/coordinator_route_probe.yml index c91acf78f2..b3754344f7 100644 --- a/.github/workflows/coordinator_route_probe.yml +++ b/.github/workflows/coordinator_route_probe.yml @@ -29,7 +29,7 @@ jobs: - name: Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: stable cache: true - name: Install dependencies diff --git a/.github/workflows/count_formatter.yaml b/.github/workflows/count_formatter.yaml index 027fb0e834..d202e44af4 100644 --- a/.github/workflows/count_formatter.yaml +++ b/.github/workflows/count_formatter.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/count_formatter" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/creator_sync.yaml b/.github/workflows/creator_sync.yaml index ddce0251a3..89453a0226 100644 --- a/.github/workflows/creator_sync.yaml +++ b/.github/workflows/creator_sync.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/creator_sync" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/curated_list_repository.yaml b/.github/workflows/curated_list_repository.yaml index 7148ba8997..df461f60e9 100644 --- a/.github/workflows/curated_list_repository.yaml +++ b/.github/workflows/curated_list_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/curated_list_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/curation_repository.yaml b/.github/workflows/curation_repository.yaml index c7d5c3e1e1..fabe5eb53a 100644 --- a/.github/workflows/curation_repository.yaml +++ b/.github/workflows/curation_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/curation_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/db_client.yaml b/.github/workflows/db_client.yaml index 61a057f546..e2f1103d06 100644 --- a/.github/workflows/db_client.yaml +++ b/.github/workflows/db_client.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/db_client" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 40 diff --git a/.github/workflows/divine_blurhash.yaml b/.github/workflows/divine_blurhash.yaml index 6ded7bf915..19b05c3463 100644 --- a/.github/workflows/divine_blurhash.yaml +++ b/.github/workflows/divine_blurhash.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/divine_blurhash" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/divine_camera.yaml b/.github/workflows/divine_camera.yaml index 69cdb13ced..87d1c2765a 100644 --- a/.github/workflows/divine_camera.yaml +++ b/.github/workflows/divine_camera.yaml @@ -24,7 +24,7 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/divine_camera" - flutter_version: "3.44.9" + flutter_version: "3.47.2" android-unit-tests: name: Android Unit Tests @@ -42,7 +42,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true - name: Setup Gradle diff --git a/.github/workflows/divine_device_attestation.yaml b/.github/workflows/divine_device_attestation.yaml index df236641af..914a2b0dc3 100644 --- a/.github/workflows/divine_device_attestation.yaml +++ b/.github/workflows/divine_device_attestation.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/divine_device_attestation" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 100 diff --git a/.github/workflows/divine_quick_actions.yaml b/.github/workflows/divine_quick_actions.yaml index ba5f66861e..86ebbf6b2d 100644 --- a/.github/workflows/divine_quick_actions.yaml +++ b/.github/workflows/divine_quick_actions.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/divine_quick_actions" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 76 diff --git a/.github/workflows/divine_status_client.yaml b/.github/workflows/divine_status_client.yaml index 954a55803c..db8679004b 100644 --- a/.github/workflows/divine_status_client.yaml +++ b/.github/workflows/divine_status_client.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/divine_status_client" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/divine_ui.yml b/.github/workflows/divine_ui.yml index affecef9c1..224b11501a 100644 --- a/.github/workflows/divine_ui.yml +++ b/.github/workflows/divine_ui.yml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/divine_ui" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/divine_video_player.yaml b/.github/workflows/divine_video_player.yaml index bf7ae03049..909c45aafe 100644 --- a/.github/workflows/divine_video_player.yaml +++ b/.github/workflows/divine_video_player.yaml @@ -24,7 +24,7 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/divine_video_player" - flutter_version: "3.44.9" + flutter_version: "3.47.2" android-unit-tests: name: Android Unit Tests @@ -42,7 +42,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true - name: Setup Gradle diff --git a/.github/workflows/dm_repository.yaml b/.github/workflows/dm_repository.yaml index 406e5a5c16..da257201cc 100644 --- a/.github/workflows/dm_repository.yaml +++ b/.github/workflows/dm_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/dm_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 93 diff --git a/.github/workflows/feed_repository.yaml b/.github/workflows/feed_repository.yaml index bc6eaf97c1..48ee605c3f 100644 --- a/.github/workflows/feed_repository.yaml +++ b/.github/workflows/feed_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/feed_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 64 diff --git a/.github/workflows/feed_tuning_repository.yaml b/.github/workflows/feed_tuning_repository.yaml index b1b839c22b..011126c7bf 100644 --- a/.github/workflows/feed_tuning_repository.yaml +++ b/.github/workflows/feed_tuning_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/feed_tuning_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/follow_repository.yaml b/.github/workflows/follow_repository.yaml index 461cdfb999..756bb875e9 100644 --- a/.github/workflows/follow_repository.yaml +++ b/.github/workflows/follow_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/follow_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/funnelcake_api_client.yaml b/.github/workflows/funnelcake_api_client.yaml index 45bb45ed10..ec7ab6d246 100644 --- a/.github/workflows/funnelcake_api_client.yaml +++ b/.github/workflows/funnelcake_api_client.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/funnelcake_api_client" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 44 diff --git a/.github/workflows/hashtag_repository.yaml b/.github/workflows/hashtag_repository.yaml index 860c91ea36..75f4ef6916 100644 --- a/.github/workflows/hashtag_repository.yaml +++ b/.github/workflows/hashtag_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/hashtag_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 30 diff --git a/.github/workflows/iap_repository.yaml b/.github/workflows/iap_repository.yaml index 56b6d5f6f9..0cfd7d68f3 100644 --- a/.github/workflows/iap_repository.yaml +++ b/.github/workflows/iap_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/iap_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 90 diff --git a/.github/workflows/image_metadata_stripper.yaml b/.github/workflows/image_metadata_stripper.yaml index c1211a5310..e618290791 100644 --- a/.github/workflows/image_metadata_stripper.yaml +++ b/.github/workflows/image_metadata_stripper.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/image_metadata_stripper" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/infinite_video_feed.yaml b/.github/workflows/infinite_video_feed.yaml index 98f805a5b0..b6b29a25ad 100644 --- a/.github/workflows/infinite_video_feed.yaml +++ b/.github/workflows/infinite_video_feed.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/infinite_video_feed" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/invite_api_client.yaml b/.github/workflows/invite_api_client.yaml index 6288607d24..f8faed8fa9 100644 --- a/.github/workflows/invite_api_client.yaml +++ b/.github/workflows/invite_api_client.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/invite_api_client" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 89 diff --git a/.github/workflows/keycast_flutter.yaml b/.github/workflows/keycast_flutter.yaml index c2c0808e41..64fbfff9a2 100644 --- a/.github/workflows/keycast_flutter.yaml +++ b/.github/workflows/keycast_flutter.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/keycast_flutter" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 44 diff --git a/.github/workflows/likes_repository.yaml b/.github/workflows/likes_repository.yaml index 94254037a9..071139057f 100644 --- a/.github/workflows/likes_repository.yaml +++ b/.github/workflows/likes_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/likes_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 62 diff --git a/.github/workflows/media_cache.yml b/.github/workflows/media_cache.yml index f0e107b91c..832e612eed 100644 --- a/.github/workflows/media_cache.yml +++ b/.github/workflows/media_cache.yml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/media_cache" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 98 diff --git a/.github/workflows/mobile_ci.yaml b/.github/workflows/mobile_ci.yaml index 51d57bea44..a6023dc12f 100644 --- a/.github/workflows/mobile_ci.yaml +++ b/.github/workflows/mobile_ci.yaml @@ -127,7 +127,7 @@ jobs: - name: 🐦 Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true - name: 📦 Install dependencies @@ -527,7 +527,7 @@ jobs: - name: 🐦 Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true - name: 📦 Install dependencies @@ -551,7 +551,7 @@ jobs: - name: 🐦 Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true - name: 📦 Install dependencies @@ -582,7 +582,7 @@ jobs: - name: 🐦 Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true - name: 📦 Install dependencies @@ -777,7 +777,7 @@ jobs: - name: 🐦 Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true - name: 📦 Install dependencies diff --git a/.github/workflows/mobile_pr_preview_build.yml b/.github/workflows/mobile_pr_preview_build.yml index be977be8a4..2f6fe2997b 100644 --- a/.github/workflows/mobile_pr_preview_build.yml +++ b/.github/workflows/mobile_pr_preview_build.yml @@ -37,7 +37,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: stable cache: true diff --git a/.github/workflows/mobile_service_integration_tests.yaml b/.github/workflows/mobile_service_integration_tests.yaml index 13106bedb8..fcb5e0d890 100644 --- a/.github/workflows/mobile_service_integration_tests.yaml +++ b/.github/workflows/mobile_service_integration_tests.yaml @@ -101,7 +101,7 @@ jobs: - name: 🐦 Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: "stable" cache: true # `flutter test integration_test/...` runs in integration-test mode, which diff --git a/.github/workflows/mobile_web_production_deploy.yml b/.github/workflows/mobile_web_production_deploy.yml index bba9cf6faa..39450a8b72 100644 --- a/.github/workflows/mobile_web_production_deploy.yml +++ b/.github/workflows/mobile_web_production_deploy.yml @@ -31,7 +31,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.9" + flutter-version: "3.47.2" channel: stable cache: true diff --git a/.github/workflows/models_ci.yaml b/.github/workflows/models_ci.yaml index edacd1eca8..21aa47baa4 100644 --- a/.github/workflows/models_ci.yaml +++ b/.github/workflows/models_ci.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/models" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 25 diff --git a/.github/workflows/nostr_app_bridge_repository.yaml b/.github/workflows/nostr_app_bridge_repository.yaml index 257d55d08e..85c1becfc1 100644 --- a/.github/workflows/nostr_app_bridge_repository.yaml +++ b/.github/workflows/nostr_app_bridge_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/nostr_app_bridge_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 78 diff --git a/.github/workflows/nostr_client.yaml b/.github/workflows/nostr_client.yaml index b71b2594e6..8b574459ab 100644 --- a/.github/workflows/nostr_client.yaml +++ b/.github/workflows/nostr_client.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/nostr_client" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 82 diff --git a/.github/workflows/nostr_key_manager.yml b/.github/workflows/nostr_key_manager.yml index d467e67e97..c8d61e8731 100644 --- a/.github/workflows/nostr_key_manager.yml +++ b/.github/workflows/nostr_key_manager.yml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/nostr_key_manager" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 52 diff --git a/.github/workflows/nostr_sdk.yaml b/.github/workflows/nostr_sdk.yaml index 190d3949f1..0e62a6ec7d 100644 --- a/.github/workflows/nostr_sdk.yaml +++ b/.github/workflows/nostr_sdk.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/nostr_sdk" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 20 diff --git a/.github/workflows/notification_repository.yaml b/.github/workflows/notification_repository.yaml index 4097637228..e4cad16d1c 100644 --- a/.github/workflows/notification_repository.yaml +++ b/.github/workflows/notification_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/notification_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 94 diff --git a/.github/workflows/people_lists_repository.yaml b/.github/workflows/people_lists_repository.yaml index 685c9acb23..a5c4f7cca0 100644 --- a/.github/workflows/people_lists_repository.yaml +++ b/.github/workflows/people_lists_repository.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/people_lists_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 95 diff --git a/.github/workflows/permissions_service.yaml b/.github/workflows/permissions_service.yaml index 1c354ec548..01c8e6d50a 100644 --- a/.github/workflows/permissions_service.yaml +++ b/.github/workflows/permissions_service.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/permissions_service" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 22 diff --git a/.github/workflows/profile_repository.yaml b/.github/workflows/profile_repository.yaml index 93d4f29b6f..06bad308e1 100644 --- a/.github/workflows/profile_repository.yaml +++ b/.github/workflows/profile_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/profile_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/reposts_repository.yaml b/.github/workflows/reposts_repository.yaml index 4108185a27..daf5eeddc9 100644 --- a/.github/workflows/reposts_repository.yaml +++ b/.github/workflows/reposts_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/reposts_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/sound_service.yaml b/.github/workflows/sound_service.yaml index 0e0e7826a9..4dbe1fc189 100644 --- a/.github/workflows/sound_service.yaml +++ b/.github/workflows/sound_service.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/sound_service" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/sounds_repository.yaml b/.github/workflows/sounds_repository.yaml index 0e8bcb0f2a..632b3e2915 100644 --- a/.github/workflows/sounds_repository.yaml +++ b/.github/workflows/sounds_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/sounds_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/text_sanitizer.yaml b/.github/workflows/text_sanitizer.yaml index 82c3bcbf0a..b6553eb0ee 100644 --- a/.github/workflows/text_sanitizer.yaml +++ b/.github/workflows/text_sanitizer.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/text_sanitizer" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/time_formatter.yaml b/.github/workflows/time_formatter.yaml index d949260b37..f666d1c2c1 100644 --- a/.github/workflows/time_formatter.yaml +++ b/.github/workflows/time_formatter.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/time_formatter" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 100 diff --git a/.github/workflows/tv_static_effect.yaml b/.github/workflows/tv_static_effect.yaml index 554c2a6230..c983cc67ab 100644 --- a/.github/workflows/tv_static_effect.yaml +++ b/.github/workflows/tv_static_effect.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/tv_static_effect" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/.github/workflows/unified_logger.yaml b/.github/workflows/unified_logger.yaml index 02232b6cd8..eae2f7d5d3 100644 --- a/.github/workflows/unified_logger.yaml +++ b/.github/workflows/unified_logger.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/unified_logger" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 40 diff --git a/.github/workflows/verifier_client.yaml b/.github/workflows/verifier_client.yaml index 57afafe267..1ebd7425f3 100644 --- a/.github/workflows/verifier_client.yaml +++ b/.github/workflows/verifier_client.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/verifier_client" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 76 diff --git a/.github/workflows/video_event_cache.yaml b/.github/workflows/video_event_cache.yaml index e5489960fc..dc82fc8aee 100644 --- a/.github/workflows/video_event_cache.yaml +++ b/.github/workflows/video_event_cache.yaml @@ -24,5 +24,5 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/video_event_cache" - flutter_version: "3.44.9" + flutter_version: "3.47.2" min_coverage: 0 diff --git a/.github/workflows/videos_repository.yaml b/.github/workflows/videos_repository.yaml index 87b0d448db..2d44bff10f 100644 --- a/.github/workflows/videos_repository.yaml +++ b/.github/workflows/videos_repository.yaml @@ -24,4 +24,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: "mobile/packages/videos_repository" - flutter_version: "3.44.9" + flutter_version: "3.47.2" diff --git a/AGENTS.md b/AGENTS.md index 88e8d4d947..1f2d3b7398 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -172,8 +172,8 @@ These issues stay report-only regardless of assignee; see `/AGENT_T - Run work from `mobile/` for Flutter commands. - The Gradle wrapper (`mobile/android/gradlew`, `gradlew.bat`, `gradle/wrapper/gradle-wrapper.jar`) is **tracked** (#7201), so a fresh clone or worktree can run gradle-based tooling without a prior `flutter build` — Flutter injects the wrapper only when the flutter tool is itself about to exec Gradle, never on `flutter pub get`. The jar is pinned to Gradle's published 8.14 checksum by `mobile/scripts/check_gradle_wrapper_checksum.sh` (runs in CI's `Generated Files` job, gated on native changes); regenerate with `./gradlew wrapper --gradle-version --distribution-type all` and update that script's header rather than editing the jar. `local.properties` stays ignored and is rewritten by `flutter pub get`. Direct Gradle use also needs `FLUTTER_ROOT` exported — see `mobile/docs/ANDROID_LOCAL_SETUP.md`. - If dependencies or the workspace change, run `flutter pub get`. -- If you touch `@riverpod`, `@freezed`, `@JsonSerializable`, `@GenerateMocks`, Drift schema, or other generated code inputs, run `dart run build_runner build --delete-conflicting-outputs` and commit the generated outputs. -- After generation, check `git status --short` and commit relevant files such as `*.g.dart`, `*.freezed.dart`, `hive_registrar.g.dart`, or other generated artifacts. +- If you touch `@riverpod`, `@JsonSerializable`, `@GenerateMocks`, Drift schema, or other generated code inputs, run `dart run build_runner build --delete-conflicting-outputs` and commit the generated outputs. +- After generation, check `git status --short` and commit relevant files such as `*.g.dart`, `*.mocks.dart`, `hive_registrar.g.dart`, or other generated artifacts. - Do not assume a targeted analyze pass is enough when generator-backed source changed. CI will fail on stale generated files even if local tests pass. - Executable `Future.delayed` calls are frozen by two code-only, per-file numeric ceilings (#8458): `mobile/scripts/check_future_delayed_ceiling.sh` covers `mobile/test`, while `mobile/scripts/check_future_delayed_production_ceiling.sh` covers `mobile/lib`. Their baselines (`future_delayed_test_counts.txt` and `future_delayed_production_counts.txt`) store `relpathcount`, so a second call in an already-listed file fails just like a new file; comments, dartdoc, and string literals are stripped first by `dart_code_only.awk`. Replace test waits with `fakeAsync` for timers/timeouts, `pumpEventQueue` for stream settling, or synchronization on the real completion boundary; replace production waits with explicit async coordination. Reducing a count fails until the lower ceiling is locked in with `UPDATE_BASELINE=1 bash mobile/scripts/check_future_delayed_ceiling.sh` or `UPDATE_BASELINE=1 bash mobile/scripts/check_future_delayed_production_ceiling.sh`; commit the updated baseline. Both run in CI only, in the `Generated Files` job. `check_uncancellable_timer_wait.sh` (#8457) is a separate zero-baseline guard for discarded `Timer` values that settle a `Completer`; do not conflate it with these call-site ratchets. - If you touch any key in an `app_*.arb` file (e.g. add a key to `mobile/lib/l10n/app_en.arb`), mirror it into every other `app_*.arb` locale — or add it to `_knownUntranslatedDebt` in `test/l10n/arb_consistency_test.dart` when translations are deferred — then run `cd mobile && flutter test test/l10n/arb_consistency_test.dart` (or the check-l10n skill). CI runs this test in the general suite, but the pre-push hook only catches it when an `app_*.arb` file changed, so do not rely on the hook alone. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a795a8359..1e0eee3699 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -142,8 +142,8 @@ effort or intent. Prerequisites: -- Flutter `^3.44.0` -- Dart `^3.12.0` +- Flutter `^3.47.0` +- Dart `^3.13.0` - Xcode for iOS work - Android Studio and Android SDK for Android work - CocoaPods for Apple platform dependencies diff --git a/README.md b/README.md index a9228e8709..5178c0dbd9 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,11 @@ The workspace is organized into around 75 packages, grouped roughly as: How it fits the Divine platform: the app speaks to Nostr relays for the social graph and events, stores and serves media through Blossom, supports Keycast-style remote signers over NIP-46, and verifies identity against `verifier.divine.video`. Persistence is local via Drift (SQLite, at-rest encrypted) and Hive. Firebase provides Crashlytics, push messaging, performance monitoring, and analytics. Code-push updates use Shorebird. -Generated code (Riverpod, Freezed, JSON serialization, Drift, Hive, mocks) is produced by `build_runner` and committed; regenerate after touching any generator input. +Generated code (Riverpod, JSON serialization, Drift, Hive, mocks) is produced by `build_runner` and committed; regenerate after touching any generator input. ## Getting started -Requirements: Flutter `^3.44.0` and the Dart SDK `^3.12.0`. The pinned toolchain version is in `mobile/mise.toml` (Flutter 3.44.0). All Flutter commands run from `mobile/`. +Requirements: Flutter `^3.47.0` and the Dart SDK `^3.13.0`. The pinned toolchain version is in `mobile/mise.toml` (Flutter 3.47.2). All Flutter commands run from `mobile/`. ```bash cd mobile @@ -69,7 +69,7 @@ flutter analyze flutter test ``` -If you touch codegen-backed sources (Riverpod, Freezed, JSON serialization, Drift, Hive, or mocks): +If you touch codegen-backed sources (Riverpod, JSON serialization, Drift, Hive, or mocks): ```bash dart run build_runner build --delete-conflicting-outputs diff --git a/codemagic.yaml b/codemagic.yaml index 2dfea2c406..b9edd56919 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -109,43 +109,9 @@ definitions: script: | flutter build ios --config-only - ruby <<'RUBY' - files = [ - 'ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift', - 'ios/Flutter/ephemeral/Packages/.packages/FlutterFramework/Package.swift', - ] - - files.each do |path| - contents = File.read(path) - updated = contents - .gsub('.iOS("13.0")', '.iOS("16.0")') - .gsub('.iOS(.v13)', '.iOS("16.0")') - has_ios_16_target = updated.match?(/\.iOS\(("16\.0"|\.v16)\)/) - - unless has_ios_16_target - if updated.match?(/^\s*platforms:\s*\[/) - abort("expected #{path} platforms block to declare iOS 16.0") - end - - updated = updated.sub( - /(let package = Package\(\n\s*name: "[^"]+",\n)/, - "\\1 platforms: [\n .iOS(\"16.0\")\n ],\n", - ) - has_ios_16_target = updated.match?(/\.iOS\(("16\.0"|\.v16)\)/) - end - - unless has_ios_16_target - abort("expected #{path} to declare iOS 16.0") - end - - if updated != contents - File.write(path, updated) - puts "patched #{path} to iOS 16.0" - else - puts "#{path} already targets iOS 16.0" - end - end - RUBY + ruby scripts/ensure_ios_swift_package_floor.rb \ + 'ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift' \ + 'ios/Flutter/ephemeral/Packages/.packages/FlutterFramework/Package.swift' - &flutter_pub_get name: Get Flutter dependencies script: flutter pub get @@ -202,8 +168,8 @@ definitions: script: | set -euo pipefail SHOREBIRD_BIN="$HOME/.shorebird/bin/shorebird" - EXPECTED_SHOREBIRD_VERSION="Shorebird 1.6.117" - EXPECTED_SHOREBIRD_REVISION="45facdd4e4b3c39e0d260107977584f0b7c66bec" + EXPECTED_SHOREBIRD_VERSION="Shorebird 1.6.120" + EXPECTED_SHOREBIRD_REVISION="5ac7f9a9a5c4a5e66a958e608da0f73e34a3d6bb" SHOREBIRD_VERSION_OUTPUT="" SHOREBIRD_REVISION="" if [ -x "$SHOREBIRD_BIN" ]; then @@ -988,7 +954,7 @@ workflows: - "NO" - "YES" environment: - flutter: 3.44.9 + flutter: 3.47.2 xcode: latest cocoapods: default groups: @@ -1000,7 +966,7 @@ workflows: # Must match `flutter:` above. Shorebird ships its own Flutter, so the # release is only reproducible if we pin it to the same version # Codemagic provisioned. - FLUTTER_VERSION: 3.44.9 + FLUTTER_VERSION: 3.47.2 SHOREBIRD_PLATFORM: ios BUNDLE_ID: co.openvine.app IOS_EXTENSION_BUNDLE_IDS: >- @@ -1062,7 +1028,7 @@ workflows: - STAGING - POC environment: - flutter: 3.44.9 + flutter: 3.47.2 xcode: latest cocoapods: default groups: @@ -1110,7 +1076,7 @@ workflows: - "NO" - "YES" environment: - flutter: 3.44.9 + flutter: 3.47.2 java: 17 groups: - zendesk_credentials @@ -1120,7 +1086,7 @@ workflows: - shorebird_credentials vars: # Must match `flutter:` above — see the iOS workflow for why. - FLUTTER_VERSION: 3.44.9 + FLUTTER_VERSION: 3.47.2 SHOREBIRD_PLATFORM: android android_signing: - divine_keystore @@ -1216,7 +1182,7 @@ workflows: - STAGING - POC environment: - flutter: 3.44.9 + flutter: 3.47.2 xcode: latest cocoapods: default groups: @@ -1227,7 +1193,7 @@ workflows: vars: # Must match `flutter:` above. Provenance verification compares this # against the Flutter version recorded for the target release. - FLUTTER_VERSION: 3.44.9 + FLUTTER_VERSION: 3.47.2 SHOREBIRD_PLATFORM: ios BUNDLE_ID: co.openvine.app IOS_EXTENSION_BUNDLE_IDS: >- @@ -1307,7 +1273,7 @@ workflows: - STAGING - POC environment: - flutter: 3.44.9 + flutter: 3.47.2 java: 17 groups: - zendesk_credentials @@ -1317,7 +1283,7 @@ workflows: vars: # Must match `flutter:` above. Provenance verification compares this # against the Flutter version recorded for the target release. - FLUTTER_VERSION: 3.44.9 + FLUTTER_VERSION: 3.47.2 SHOREBIRD_PLATFORM: android android_signing: - divine_keystore @@ -1365,7 +1331,7 @@ workflows: - "NO" - "YES" environment: - flutter: 3.44.9 + flutter: 3.47.2 xcode: latest cocoapods: default groups: @@ -1409,7 +1375,7 @@ workflows: max_build_duration: 25 instance_type: mac_mini_m2 environment: - flutter: 3.44.9 + flutter: 3.47.2 # Pinned alongside MAESTRO_VERSION. The compatibility contract has two # sides: 2.1.0 cannot drive Xcode 26.x, and `latest` floating means a # Codemagic image bump can break the suite with no commit on our side — @@ -1488,7 +1454,7 @@ workflows: max_build_duration: 60 instance_type: linux_x2 environment: - flutter: 3.44.9 + flutter: 3.47.2 java: 17 groups: - zendesk_credentials diff --git a/docs/BUILD_SCRIPTS_README.md b/docs/BUILD_SCRIPTS_README.md index 0501c81829..9333bc2627 100644 --- a/docs/BUILD_SCRIPTS_README.md +++ b/docs/BUILD_SCRIPTS_README.md @@ -94,7 +94,7 @@ macOS equivalent: ## Fast local workflow - `./build_ios.sh debug` and `./build_macos.sh debug` do not run `build_runner` by default. -- Use `--codegen` only when generated sources changed (freezed/riverpod/json_serializable/mocks). +- Use `--codegen` only when generated sources changed (riverpod/json_serializable/mocks). - Use `--pod-reset` only when CocoaPods is genuinely out of sync. - Use `flutter clean` or full pod removal only for unrecoverable local state issues. diff --git a/docs/BUILD_SPEED_CHECKLIST.md b/docs/BUILD_SPEED_CHECKLIST.md index 04edeb67a9..8651250e16 100644 --- a/docs/BUILD_SPEED_CHECKLIST.md +++ b/docs/BUILD_SPEED_CHECKLIST.md @@ -41,7 +41,7 @@ or `flutter clean` — re-arms the next codegen pass. The biggest local-build cost is `dart run build_runner build` (a few hundred seconds on a cold analyzer). For active development on -`@riverpod` / `@freezed` / `@JsonSerializable` / `drift` inputs, run the +`@riverpod` / `@JsonSerializable` / `drift` inputs, run the watcher in a separate terminal once and leave it running: ```bash diff --git a/docs/FLUTTER.md b/docs/FLUTTER.md index 0680c502c7..74844924db 100644 --- a/docs/FLUTTER.md +++ b/docs/FLUTTER.md @@ -359,7 +359,7 @@ class CounterView extends StatelessWidget { `pubspec.yaml` by specifying `sdk: flutter`. * **Mocks:** Prefer fakes or stubs over mocks. If mocks are absolutely necessary, use `mockito` or `mocktail` to create mocks for dependencies. While - code generation is common for state management (e.g., with `freezed`), try to + code generation is common for state management (for example Riverpod), try to avoid it for mocks. * **Coverage:** Aim for high test coverage. diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index 4031c5e2ec..d94dbbf09f 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -9,6 +9,12 @@ analyzer: - "**/generated/**" - "**/l10n/**" - "build/**" + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** errors: avoid_catches_without_on_clauses: ignore avoid_positional_boolean_parameters: ignore diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 6ee3ef8def..603729a11f 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -109,11 +109,12 @@ Nothing upstream has changed since. That measurement was taken on Flutter 3.44.0, with both cited issues already closed - so this is not a re-evaluation after an engine - upgrade. The Flutter pin has since moved to 3.44.9 (#7202), - which the profiling above predates. What changed is the - requirement: the video editor's live chroma-key preview - needs `ui.ImageFilter.shader`, which Skia does not implement - at all. On Skia the preview degrades to the unkeyed video + upgrade. The Flutter pin has since moved on twice (3.44.9 + in #7202, then 3.47.2), both of which the profiling above + predates. What changed is the requirement: the video + editor's live chroma-key preview needs + `ui.ImageFilter.shader`, which Skia does not implement at + all. On Skia the preview degrades to the unkeyed video (the exported file is unaffected either way), and we decided a working preview is worth re-entering Impeller. diff --git a/mobile/build_android.sh b/mobile/build_android.sh index d4e2d86b53..350e8a7a31 100755 --- a/mobile/build_android.sh +++ b/mobile/build_android.sh @@ -107,7 +107,7 @@ echo -e "${YELLOW}Cleaning previous builds...${NC}" flutter clean flutter pub get -# Generate code (Riverpod providers, Freezed models, etc.) +# Generate code (Riverpod providers, JSON models, etc.) echo -e "${YELLOW}Generating code with build_runner...${NC}" dart run build_runner build --delete-conflicting-outputs diff --git a/mobile/build_web_optimized.sh b/mobile/build_web_optimized.sh index 21e85448b1..8c372a212b 100755 --- a/mobile/build_web_optimized.sh +++ b/mobile/build_web_optimized.sh @@ -8,7 +8,7 @@ echo "Building optimized Flutter web app..." flutter clean flutter pub get -# Generate code (Riverpod providers, Freezed models, etc.) +# Generate code (Riverpod providers, JSON models, etc.) echo "🔧 Generating code with build_runner..." dart run build_runner build --delete-conflicting-outputs @@ -22,4 +22,4 @@ flutter build web \ # Copy updated headers file to build output cp web-deploy/_headers build/web/_headers -echo "Build complete! Deploy the build/web directory to Cloudflare." \ No newline at end of file +echo "Build complete! Deploy the build/web directory to Cloudflare." diff --git a/mobile/docs/SHOREBIRD_CODE_PUSH.md b/mobile/docs/SHOREBIRD_CODE_PUSH.md index c4aa83819b..cf4f812e56 100644 --- a/mobile/docs/SHOREBIRD_CODE_PUSH.md +++ b/mobile/docs/SHOREBIRD_CODE_PUSH.md @@ -106,7 +106,7 @@ The bundled texts were copied from `shorebirdtech/updater` at commit to each local text asset (upstream license blob ids `6802bc4b80c0f8df1413d55b16267c0969e352c9` and `a7e77cb28d386ec6eddeaabf441f91473ddefa1e`). The store engine paired with the -current Flutter 3.44.9 pin is `27bc060323bfdfe2f5b6732174d4e499e74eca70`. +current Flutter 3.47.2 pin is `315ff242617023c1c5549878bf17fad51ffec6f3`. When Flutter or Shorebird changes, re-check attribution without producing a store artifact: @@ -130,10 +130,17 @@ duplicates the workflow's `flutter:` setting and is passed to bumping Flutter, confirm the target is in `shorebird flutter versions list` first — Shorebird supports a subset. -The current 3.44.9 pin is deliberate. Shorebird's 3.44.0 engine crashed at -launch in the Dart async FFI path used by `cupertino_http`, so the repo and all -CI workflows moved together to the refreshed 3.44.9 Shorebird engine. Do not -move the pin back independently in one workflow. +The Codemagic installer also pins the Shorebird CLI version and its full source +revision. That CLI must be the release that introduced support for the selected +Flutter version or newer. Flutter 3.47.2 is paired with Shorebird 1.6.120 at +revision `5ac7f9a9a5c4a5e66a958e608da0f73e34a3d6bb`; update the pin and its +configuration test together when either side moves. + +The pin moves as one unit across the repo, every CI workflow, and every +Codemagic workflow. The rule is not cosmetic: while the repo ran Flutter 3.44, +Shorebird's 3.44.0 engine crashed at launch in the Dart async FFI path used by +`cupertino_http`, and recovering meant moving every pin together to the +refreshed 3.44.9 engine. Do not move the pin in one workflow independently. Patch commands take no `--flutter-version`. A patch is compiled against whatever version its release used, which Shorebird resolves from the release. diff --git a/mobile/integration_test/auth/secure_account_test.dart b/mobile/integration_test/auth/secure_account_test.dart index c899c31d6c..fca2b8820c 100644 --- a/mobile/integration_test/auth/secure_account_test.dart +++ b/mobile/integration_test/auth/secure_account_test.dart @@ -119,8 +119,7 @@ void main() { expect( textFields, findsNWidgets(3), - reason: - 'Secure account screen should show email, password, and confirmation', + reason: 'Secure account screen should show email, password, and confirmation', ); await tester.enterText(textFields.at(0), testEmail); diff --git a/mobile/integration_test/perf/share_dm_send_isolate_benchmark_test.dart b/mobile/integration_test/perf/share_dm_send_isolate_benchmark_test.dart index da81df741e..2d2b808569 100644 --- a/mobile/integration_test/perf/share_dm_send_isolate_benchmark_test.dart +++ b/mobile/integration_test/perf/share_dm_send_isolate_benchmark_test.dart @@ -121,8 +121,7 @@ void main() { expect( newStallMs, lessThan(frameBudgetMs), - reason: - 'compute() offload should keep the UI isolate under one frame budget', + reason: 'compute() offload should keep the UI isolate under one frame budget', ); }); }); diff --git a/mobile/lib/blocs/account_deletion_recovery/account_deletion_recovery_cubit.dart b/mobile/lib/blocs/account_deletion_recovery/account_deletion_recovery_cubit.dart index bc97276467..3aea58ea6b 100644 --- a/mobile/lib/blocs/account_deletion_recovery/account_deletion_recovery_cubit.dart +++ b/mobile/lib/blocs/account_deletion_recovery/account_deletion_recovery_cubit.dart @@ -32,8 +32,10 @@ abstract class AccountDeletionRecoveryPolling { tickIndex < schedule.length ? schedule[tickIndex] : cap; } -typedef RecoveryTimerFactory = - Timer Function(Duration duration, void Function() callback); +typedef RecoveryTimerFactory = Timer Function( + Duration duration, + void Function() callback, +); class AccountDeletionRecoveryCubit extends Cubit with CloseGuardedEmit { diff --git a/mobile/lib/blocs/bug_report/bug_report_cubit.dart b/mobile/lib/blocs/bug_report/bug_report_cubit.dart index 2e263214f1..f0b3315bce 100644 --- a/mobile/lib/blocs/bug_report/bug_report_cubit.dart +++ b/mobile/lib/blocs/bug_report/bug_report_cubit.dart @@ -11,21 +11,20 @@ import 'package:unified_logger/unified_logger.dart'; /// Cubit doesn't reach into the static `ZendeskSupportService` surface /// directly. Tests inject a fake; production wires /// `ZendeskSupportService.createStructuredBugReport`. -typedef SubmitBugReportAction = - Future Function({ - required String subject, - required String description, - required String reportId, - required String appVersion, - required Map deviceInfo, - String? stepsToReproduce, - String? expectedBehavior, - String? currentScreen, - String? userPubkey, - Map? errorCounts, - String? logsSummary, - List? attachmentPaths, - }); +typedef SubmitBugReportAction = Future Function({ + required String subject, + required String description, + required String reportId, + required String appVersion, + required Map deviceInfo, + String? stepsToReproduce, + String? expectedBehavior, + String? currentScreen, + String? userPubkey, + Map? errorCounts, + String? logsSummary, + List? attachmentPaths, +}); /// Builds the logs summary string the cubit passes to Zendesk. typedef BuildLogsSummary = Future Function(List logs); diff --git a/mobile/lib/blocs/comments/comment_composer/comment_composer_bloc.dart b/mobile/lib/blocs/comments/comment_composer/comment_composer_bloc.dart index b72b9138ef..cdce3f17ae 100644 --- a/mobile/lib/blocs/comments/comment_composer/comment_composer_bloc.dart +++ b/mobile/lib/blocs/comments/comment_composer/comment_composer_bloc.dart @@ -25,12 +25,11 @@ part 'comment_composer_state.dart'; /// #5854: replies rendered off-screen led posters to re-send the same reply, /// spawning duplicates. The scroll-into-view fix stops that at the source; this /// checker is the defense-in-depth net for stray double-submits. -typedef DuplicateCommentChecker = - bool Function({ - required String content, - required String authorPubkey, - String? parentCommentId, - }); +typedef DuplicateCommentChecker = bool Function({ + required String content, + required String authorPubkey, + String? parentCommentId, +}); /// BLoC owning composer input state for one video's comments: main / reply / /// edit text buffers, mention search (`restartable()`), publish + edit flows diff --git a/mobile/lib/blocs/feature_request/feature_request_cubit.dart b/mobile/lib/blocs/feature_request/feature_request_cubit.dart index 024e4dd19a..e9d34ff961 100644 --- a/mobile/lib/blocs/feature_request/feature_request_cubit.dart +++ b/mobile/lib/blocs/feature_request/feature_request_cubit.dart @@ -9,14 +9,13 @@ import 'package:unified_logger/unified_logger.dart'; /// doesn't need to know about the static `ZendeskSupportService` surface /// directly. Tests inject a fake; production wires /// `ZendeskSupportService.createFeatureRequest`. -typedef SubmitFeatureRequestAction = - Future Function({ - required String subject, - required String description, - required String usefulness, - required String whenToUse, - String? userPubkey, - }); +typedef SubmitFeatureRequestAction = Future Function({ + required String subject, + required String description, + required String usefulness, + required String whenToUse, + String? userPubkey, +}); /// Cubit backing the feature request flow. Owns only the submission /// lifecycle (`idle / submitting / success / failure`); the four diff --git a/mobile/lib/blocs/fullscreen_feed/fullscreen_feed_bloc.dart b/mobile/lib/blocs/fullscreen_feed/fullscreen_feed_bloc.dart index 37bb82fa4e..15e42827fe 100644 --- a/mobile/lib/blocs/fullscreen_feed/fullscreen_feed_bloc.dart +++ b/mobile/lib/blocs/fullscreen_feed/fullscreen_feed_bloc.dart @@ -44,12 +44,11 @@ typedef OnVideoConfirmedUnavailable = void Function(VideoEvent video); /// verdict is persisted. Quarantined and age-restricted blobs also 404 but /// are reversible, so they keep their error tile. Tests may inject a smaller /// predicate. -typedef ConfirmVideoUnavailable = - Future Function({ - required String videoId, - required String? videoUrl, - String? explicitSha256, - }); +typedef ConfirmVideoUnavailable = Future Function({ + required String videoId, + required String? videoUrl, + String? explicitSha256, +}); /// Returns `true` when [video] must be hidden (blocked / muted / blocked-us / /// muted-us authors or reposters, plus shared feed policy). Injected so the diff --git a/mobile/lib/blocs/profile_feed/profile_feed_cubit.dart b/mobile/lib/blocs/profile_feed/profile_feed_cubit.dart index 52164dd5e2..78caf04b18 100644 --- a/mobile/lib/blocs/profile_feed/profile_feed_cubit.dart +++ b/mobile/lib/blocs/profile_feed/profile_feed_cubit.dart @@ -41,8 +41,9 @@ EventTransformer _auditSequential(Duration duration) { /// Enriches REST-sourced videos with their full Nostr tag set. Injected so the /// cubit stays decoupled from `NostrClient` / the enrichment util and testable. -typedef EnrichVideos = - Future> Function(List videos); +typedef EnrichVideos = Future> Function( + List videos, +); /// Bloc backing a single author's profile feed. /// diff --git a/mobile/lib/blocs/safety_settings/safety_settings_cubit.dart b/mobile/lib/blocs/safety_settings/safety_settings_cubit.dart index 10d336cd1c..bc81358ada 100644 --- a/mobile/lib/blocs/safety_settings/safety_settings_cubit.dart +++ b/mobile/lib/blocs/safety_settings/safety_settings_cubit.dart @@ -3,6 +3,7 @@ // ABOUTME: divine-hosted-only) plus reactive labeler and blocklist lists. import 'dart:async'; + import 'package:content_blocklist_repository/content_blocklist_repository.dart'; import 'package:content_policy/content_policy.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/mobile/lib/blocs/saved_sounds/saved_sound_media_probe.dart b/mobile/lib/blocs/saved_sounds/saved_sound_media_probe.dart index 09858f1640..358fbba813 100644 --- a/mobile/lib/blocs/saved_sounds/saved_sound_media_probe.dart +++ b/mobile/lib/blocs/saved_sounds/saved_sound_media_probe.dart @@ -22,10 +22,12 @@ class SavedSoundMediaResult { final List waveformSamples; } -typedef SavedSoundWaveformReader = - Future Function(WaveformConfigs configs); -typedef SavedSoundMetadataReader = - Future Function(EditorVideo source); +typedef SavedSoundWaveformReader = Future Function( + WaveformConfigs configs, +); +typedef SavedSoundMetadataReader = Future Function( + EditorVideo source, +); class ProVideoEditorSavedSoundMediaProbe implements SavedSoundMediaProbe { ProVideoEditorSavedSoundMediaProbe({ diff --git a/mobile/lib/blocs/user_search/user_search_bloc.dart b/mobile/lib/blocs/user_search/user_search_bloc.dart index 8c57c1b64c..d7f91e1306 100644 --- a/mobile/lib/blocs/user_search/user_search_bloc.dart +++ b/mobile/lib/blocs/user_search/user_search_bloc.dart @@ -21,15 +21,14 @@ part 'user_search_state.dart'; const _pageSize = 50; int _nextSearchCorrelationId = 0; -typedef UserSearchRunner = - Stream Function({ - required String query, - required int limit, - required String sortBy, - required bool hasVideos, - required Set? boostPubkeys, - required SearchCancellationToken cancellationToken, - }); +typedef UserSearchRunner = Stream Function({ + required String query, + required int limit, + required String sortBy, + required bool hasVideos, + required Set? boostPubkeys, + required SearchCancellationToken cancellationToken, +}); Map _pendingSourceOutcomes() { return { diff --git a/mobile/lib/blocs/video_editor/chroma_key/chroma_key_editor_cubit.dart b/mobile/lib/blocs/video_editor/chroma_key/chroma_key_editor_cubit.dart index 9fff666d87..d4c1875001 100644 --- a/mobile/lib/blocs/video_editor/chroma_key/chroma_key_editor_cubit.dart +++ b/mobile/lib/blocs/video_editor/chroma_key/chroma_key_editor_cubit.dart @@ -21,8 +21,9 @@ part 'chroma_key_editor_state.dart'; /// app it is [ChromaKey.detect], which samples a ring around the frame border /// through the thumbnail pipeline — a metadata call, three thumbnails from a /// quarter, half and three quarters through, and a decode each. No render. -typedef ChromaKeyDetectFn = - Future Function(EditorVideo video); +typedef ChromaKeyDetectFn = Future Function( + EditorVideo video, +); /// The key a clip starts from before anything is measured or adjusted. /// @@ -55,9 +56,7 @@ class ChromaKeyEditorCubit extends Cubit @visibleForTesting bool detectOnOpen = true, }) : _video = video, _detect = detect, - super( - ChromaKeyEditorState(chromaKey: initialChromaKey ?? _initialKey), - ) { + super(ChromaKeyEditorState(chromaKey: initialChromaKey ?? _initialKey)) { // Measuring beats guessing, at a metadata round trip and three decoded // thumbnails, so the screen opens on a real cutout — or on the reason // there isn't one — instead of an inert panel the user has to know to @@ -113,15 +112,12 @@ class ChromaKeyEditorCubit extends Cubit // Same split as the bake in `ClipEditorBloc`: a decode or channel failure // is expected and stays out of Crashlytics, an invariant violation does // not. - _reportDetectionFailure( - switch (error) { - StateError() || - TypeError() || - RangeError() => Reportable(error, context: 'detectFromFootage'), - _ => error, - }, - stackTrace, - ); + _reportDetectionFailure(switch (error) { + StateError() || + TypeError() || + RangeError() => Reportable(error, context: 'detectFromFootage'), + _ => error, + }, stackTrace); return; } diff --git a/mobile/lib/blocs/video_editor/clip_editor/clip_editor_bloc.dart b/mobile/lib/blocs/video_editor/clip_editor/clip_editor_bloc.dart index 3675e546fe..3bc390d091 100644 --- a/mobile/lib/blocs/video_editor/clip_editor/clip_editor_bloc.dart +++ b/mobile/lib/blocs/video_editor/clip_editor/clip_editor_bloc.dart @@ -30,26 +30,24 @@ part 'clip_editor_state.dart'; /// Function signature matching [VideoEditorSplitService.splitClip], used as /// an injectable seam so tests can swap in a pure-Dart fake that does not /// touch `path_provider` or `pro_video_editor` plugins. -typedef SplitClipFn = - Future Function({ - required DivineVideoClip sourceClip, - required Duration splitPosition, - required void Function( - DivineVideoClip startClip, - DivineVideoClip endClip, - )? - onClipsCreated, - required void Function(DivineVideoClip clip, String thumbnailPath)? - onThumbnailExtracted, - }); +typedef SplitClipFn = Future Function({ + required DivineVideoClip sourceClip, + required Duration splitPosition, + required void Function( + DivineVideoClip startClip, + DivineVideoClip endClip, + )? + onClipsCreated, + required void Function(DivineVideoClip clip, String thumbnailPath)? + onThumbnailExtracted, +}); /// Function signature matching [VideoEditorReverseService.reverseClip], used as /// an injectable seam so tests can swap in a pure-Dart fake. -typedef ReverseClipFn = - Future Function({ - required DivineVideoClip sourceClip, - required String renderId, - }); +typedef ReverseClipFn = Future Function({ + required DivineVideoClip sourceClip, + required String renderId, +}); /// Function signature matching /// [StopMotionFrameTransformService.writeTransformedFrame], injectable so tests @@ -58,43 +56,39 @@ typedef WriteStopMotionFrameFn = Future Function(Uint8List bytes); /// Function signature matching [VideoEditorTransformService.transformClip], /// used as an injectable seam so tests can swap in a pure-Dart fake. -typedef TransformClipFn = - Future Function({ - required DivineVideoClip sourceClip, - required ExportTransform transform, - required String renderId, - }); +typedef TransformClipFn = Future Function({ + required DivineVideoClip sourceClip, + required ExportTransform transform, + required String renderId, +}); /// Function signature matching [VideoEditorMergeService.mergeClips], used as an /// injectable seam so tests can swap in a pure-Dart fake that does not touch /// the render pipeline. -typedef MergeClipsFn = - Future Function({ - required List clips, - required String renderId, - }); +typedef MergeClipsFn = Future Function({ + required List clips, + required String renderId, +}); /// Function signature matching /// [VideoEditorClipLibrarySaveService.flattenClipForLibrary], used as an /// injectable seam so tests can swap in a pure-Dart fake that does not touch /// `path_provider` or `pro_video_editor` plugins. -typedef FlattenClipForLibraryFn = - Future Function({ - required DivineVideoClip clip, - required String renderId, - EditorOverlaySnapshot? overlays, - }); +typedef FlattenClipForLibraryFn = Future Function({ + required DivineVideoClip clip, + required String renderId, + EditorOverlaySnapshot? overlays, +}); /// Function signature matching /// [VideoEditorClipLibrarySaveService.cleanupFlattenedClip], the injectable /// seam that deletes a flattened clip's documents-dir files when the save /// doesn't reach the library, so tests can assert cleanup without touching the /// file system. -typedef CleanupFlattenedClipFn = - Future Function( - DivineVideoClip flattened, { - String? keepThumbnailPath, - }); +typedef CleanupFlattenedClipFn = Future Function( + DivineVideoClip flattened, { + String? keepThumbnailPath, +}); /// Queues files that became unreachable when a clip's file references were /// rewritten — a transform's pre-transform render, a bake's previous output, @@ -115,8 +109,9 @@ void _noopDeferFileCleanup(Iterable paths) {} /// library lives behind a Riverpod provider this BLoC cannot reach, so the /// dependency arrives as a callback — the same transition seam that already /// brings the clip list in (see [ClipEditorBloc]). -typedef SaveClipToLibraryFn = - Future Function({required DivineVideoClip clip}); +typedef SaveClipToLibraryFn = Future Function({ + required DivineVideoClip clip, +}); /// BLoC for managing video clip editor state. /// diff --git a/mobile/lib/blocs/video_editor/transition_boundary/transition_boundary_cubit.dart b/mobile/lib/blocs/video_editor/transition_boundary/transition_boundary_cubit.dart index 99a4239eb2..dc5af12b5e 100644 --- a/mobile/lib/blocs/video_editor/transition_boundary/transition_boundary_cubit.dart +++ b/mobile/lib/blocs/video_editor/transition_boundary/transition_boundary_cubit.dart @@ -19,8 +19,10 @@ const _boundaryFrameEpsilon = Duration(milliseconds: 50); /// path or `null` when it can't be produced. Injectable so the emit-on-extract /// and keep-placeholder-on-null paths can be tested without the real thumbnail /// pipeline / file system. -typedef BoundaryFrameExtractor = - Future Function(DivineVideoClip clip, {required bool tail}); +typedef BoundaryFrameExtractor = Future Function( + DivineVideoClip clip, { + required bool tail, +}); /// Resolves the two frames a transition preview shows either side of the /// boundary: the outgoing clip's last visible frame and the incoming clip's diff --git a/mobile/lib/blocs/video_feed/video_feed_bloc.dart b/mobile/lib/blocs/video_feed/video_feed_bloc.dart index 66cbd00ae0..6a29add791 100644 --- a/mobile/lib/blocs/video_feed/video_feed_bloc.dart +++ b/mobile/lib/blocs/video_feed/video_feed_bloc.dart @@ -33,8 +33,9 @@ const _defaultAutoRefreshMinInterval = Duration(minutes: 10); /// /// Injected so [VideoFeedBloc] stays decoupled from relay clients while still /// letting home feeds repair compact REST rows that omit ProofMode/C2PA tags. -typedef EnrichVideos = - Future> Function(List videos); +typedef EnrichVideos = Future> Function( + List videos, +); /// BLoC for managing the unified video feed. /// diff --git a/mobile/lib/features/oauth/app_oauth_callback.dart b/mobile/lib/features/oauth/app_oauth_callback.dart index f898cbc628..8939141250 100644 --- a/mobile/lib/features/oauth/app_oauth_callback.dart +++ b/mobile/lib/features/oauth/app_oauth_callback.dart @@ -31,12 +31,11 @@ final _authorityTerminator = RegExp('[/#?]'); final _malformedPercentTriplet = RegExp('%(?![0-9A-Fa-f]{2})'); /// Authentication seam matching the arguments used by [FlutterWebAuth2]. -typedef AppOAuthAuthenticate = - Future Function({ - required String url, - required String callbackUrlScheme, - required FlutterWebAuth2Options options, - }); +typedef AppOAuthAuthenticate = Future Function({ + required String url, + required String callbackUrlScheme, + required FlutterWebAuth2Options options, +}); /// Opens [authorizationUri] in the native OAuth session and returns the /// callback URL, or null when the user dismissed the session. diff --git a/mobile/lib/features/post_publish/post_publish_experiment.dart b/mobile/lib/features/post_publish/post_publish_experiment.dart index 7bdf7561d6..34de47787f 100644 --- a/mobile/lib/features/post_publish/post_publish_experiment.dart +++ b/mobile/lib/features/post_publish/post_publish_experiment.dart @@ -16,8 +16,9 @@ enum PostPublishVariant { final String analyticsName; } -typedef RecordPostPublishExposure = - Future Function(PostPublishVariant variant); +typedef RecordPostPublishExposure = Future Function( + PostPublishVariant variant, +); /// Marks a publish that should get the full confirmation — a sheet offering /// View and Share — rather than the bare snackbar the control arm sees. diff --git a/mobile/lib/l10n/generated/app_localizations_am.dart b/mobile/lib/l10n/generated/app_localizations_am.dart index 0815561424..21ae53075b 100644 --- a/mobile/lib/l10n/generated/app_localizations_am.dart +++ b/mobile/lib/l10n/generated/app_localizations_am.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint diff --git a/mobile/lib/l10n/generated/app_localizations_ar.dart b/mobile/lib/l10n/generated/app_localizations_ar.dart index e88bfa5808..3f901987d8 100644 --- a/mobile/lib/l10n/generated/app_localizations_ar.dart +++ b/mobile/lib/l10n/generated/app_localizations_ar.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9809,8 +9810,7 @@ class AppLocalizationsAr extends AppLocalizations { locale: localeName, other: 'ستُحظر $count حسابات تضع هذه الشارة الآن. لن تظهر منشوراتها في تغذياتك ولن يتم إبلاغها.', - one: - 'سيُحظر الحساب الذي يضع هذه الشارة الآن. لن تظهر منشوراته في تغذياتك ولن يتم إبلاغه.', + one: 'سيُحظر الحساب الذي يضع هذه الشارة الآن. لن تظهر منشوراته في تغذياتك ولن يتم إبلاغه.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_bg.dart b/mobile/lib/l10n/generated/app_localizations_bg.dart index 8e8d6f08f2..e15fcbec4f 100644 --- a/mobile/lib/l10n/generated/app_localizations_bg.dart +++ b/mobile/lib/l10n/generated/app_localizations_bg.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9968,8 +9969,7 @@ class AppLocalizationsBg extends AppLocalizations { locale: localeName, other: 'Това блокира $count акаунта, които в момента заявяват тази значка. Публикациите им ще излязат от твоите фийдове и няма да получат известие.', - one: - 'Това блокира акаунта, който в момента заявява тази значка. Публикациите му ще излязат от твоите фийдове и няма да получи известие.', + one: 'Това блокира акаунта, който в момента заявява тази значка. Публикациите му ще излязат от твоите фийдове и няма да получи известие.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_de.dart b/mobile/lib/l10n/generated/app_localizations_de.dart index 006358cce5..1490ff7787 100644 --- a/mobile/lib/l10n/generated/app_localizations_de.dart +++ b/mobile/lib/l10n/generated/app_localizations_de.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9996,8 +9997,7 @@ class AppLocalizationsDe extends AppLocalizations { locale: localeName, other: 'Das blockiert die $count Konten, die dieses Badge gerade tragen. Ihre Beiträge tauchen nicht mehr in deinen Feeds auf, und sie werden nicht informiert.', - one: - 'Das blockiert das Konto, das dieses Badge gerade trägt. Seine Beiträge tauchen nicht mehr in deinen Feeds auf, und es wird nicht informiert.', + one: 'Das blockiert das Konto, das dieses Badge gerade trägt. Seine Beiträge tauchen nicht mehr in deinen Feeds auf, und es wird nicht informiert.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_en.dart b/mobile/lib/l10n/generated/app_localizations_en.dart index 4bf4302654..88c34fcc19 100644 --- a/mobile/lib/l10n/generated/app_localizations_en.dart +++ b/mobile/lib/l10n/generated/app_localizations_en.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9974,8 +9975,7 @@ class AppLocalizationsEn extends AppLocalizations { locale: localeName, other: 'This blocks the $count accounts currently claiming this badge. Their posts will leave your feeds, and they will not be notified.', - one: - 'This blocks the account currently claiming this badge. Their posts will leave your feeds, and they will not be notified.', + one: 'This blocks the account currently claiming this badge. Their posts will leave your feeds, and they will not be notified.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_es.dart b/mobile/lib/l10n/generated/app_localizations_es.dart index aada5804a5..b069c0141a 100644 --- a/mobile/lib/l10n/generated/app_localizations_es.dart +++ b/mobile/lib/l10n/generated/app_localizations_es.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9975,8 +9976,7 @@ class AppLocalizationsEs extends AppLocalizations { locale: localeName, other: 'Esto bloquea las $count cuentas que llevan esta insignia ahora mismo. Sus publicaciones no van a aparecer en tus feeds y no les vamos a avisar.', - one: - 'Esto bloquea la cuenta que lleva esta insignia ahora mismo. Sus publicaciones no van a aparecer en tus feeds y no le vamos a avisar.', + one: 'Esto bloquea la cuenta que lleva esta insignia ahora mismo. Sus publicaciones no van a aparecer en tus feeds y no le vamos a avisar.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_fil.dart b/mobile/lib/l10n/generated/app_localizations_fil.dart index 166d748d10..3c3a96d9d0 100644 --- a/mobile/lib/l10n/generated/app_localizations_fil.dart +++ b/mobile/lib/l10n/generated/app_localizations_fil.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9953,8 +9954,7 @@ class AppLocalizationsFil extends AppLocalizations { locale: localeName, other: 'Iba-block nito ang $count account na kasalukuyang nag-claim ng badge na ito. Mawawala ang posts nila sa feeds mo, at hindi sila aabisuhan.', - one: - 'Iba-block nito ang account na kasalukuyang nag-claim ng badge na ito. Mawawala ang posts nila sa feeds mo, at hindi sila aabisuhan.', + one: 'Iba-block nito ang account na kasalukuyang nag-claim ng badge na ito. Mawawala ang posts nila sa feeds mo, at hindi sila aabisuhan.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_fr.dart b/mobile/lib/l10n/generated/app_localizations_fr.dart index 857a51c847..69cedd1e67 100644 --- a/mobile/lib/l10n/generated/app_localizations_fr.dart +++ b/mobile/lib/l10n/generated/app_localizations_fr.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -10020,8 +10021,7 @@ class AppLocalizationsFr extends AppLocalizations { locale: localeName, other: 'Cela bloque les $count comptes qui portent ce badge en ce moment. Leurs posts n\'apparaîtront plus dans tes fils, et ils ne seront pas prévenus.', - one: - 'Cela bloque le compte qui porte ce badge en ce moment. Ses posts n\'apparaîtront plus dans tes fils, et il ne sera pas prévenu.', + one: 'Cela bloque le compte qui porte ce badge en ce moment. Ses posts n\'apparaîtront plus dans tes fils, et il ne sera pas prévenu.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_id.dart b/mobile/lib/l10n/generated/app_localizations_id.dart index 864a8f8e4e..a2c3fcb3eb 100644 --- a/mobile/lib/l10n/generated/app_localizations_id.dart +++ b/mobile/lib/l10n/generated/app_localizations_id.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9794,8 +9795,7 @@ class AppLocalizationsId extends AppLocalizations { locale: localeName, other: 'Ini memblokir $count akun yang sedang mengklaim lencana ini. Postingan mereka akan keluar dari feed-mu, dan mereka tidak akan diberi tahu.', - one: - 'Ini memblokir akun yang sedang mengklaim lencana ini. Postingannya akan keluar dari feed-mu, dan mereka tidak akan diberi tahu.', + one: 'Ini memblokir akun yang sedang mengklaim lencana ini. Postingannya akan keluar dari feed-mu, dan mereka tidak akan diberi tahu.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_it.dart b/mobile/lib/l10n/generated/app_localizations_it.dart index 929d7e2841..43d89ff64c 100644 --- a/mobile/lib/l10n/generated/app_localizations_it.dart +++ b/mobile/lib/l10n/generated/app_localizations_it.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9987,8 +9988,7 @@ class AppLocalizationsIt extends AppLocalizations { locale: localeName, other: 'Questo blocca i $count account che ora tengono questo badge sul profilo. I loro post non appariranno nei tuoi feed e non verranno avvisati.', - one: - 'Questo blocca l\'account che ora tiene questo badge sul profilo. I suoi post non appariranno nei tuoi feed e non verrà avvisato.', + one: 'Questo blocca l\'account che ora tiene questo badge sul profilo. I suoi post non appariranno nei tuoi feed e non verrà avvisato.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_ja.dart b/mobile/lib/l10n/generated/app_localizations_ja.dart index 3f475374d8..97e909890b 100644 --- a/mobile/lib/l10n/generated/app_localizations_ja.dart +++ b/mobile/lib/l10n/generated/app_localizations_ja.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint diff --git a/mobile/lib/l10n/generated/app_localizations_ko.dart b/mobile/lib/l10n/generated/app_localizations_ko.dart index b30186f2d5..0d7e58e88e 100644 --- a/mobile/lib/l10n/generated/app_localizations_ko.dart +++ b/mobile/lib/l10n/generated/app_localizations_ko.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint diff --git a/mobile/lib/l10n/generated/app_localizations_ms.dart b/mobile/lib/l10n/generated/app_localizations_ms.dart index c6cc48419e..dc0bdc19de 100644 --- a/mobile/lib/l10n/generated/app_localizations_ms.dart +++ b/mobile/lib/l10n/generated/app_localizations_ms.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9885,8 +9886,7 @@ class AppLocalizationsMs extends AppLocalizations { locale: localeName, other: 'Ini menyekat $count akaun yang sedang memegang lencana ini. Siaran mereka tidak akan muncul dalam suapan anda dan mereka tidak akan diberitahu.', - one: - 'Ini menyekat akaun yang sedang memegang lencana ini. Siaran mereka tidak akan muncul dalam suapan anda dan mereka tidak akan diberitahu.', + one: 'Ini menyekat akaun yang sedang memegang lencana ini. Siaran mereka tidak akan muncul dalam suapan anda dan mereka tidak akan diberitahu.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_nl.dart b/mobile/lib/l10n/generated/app_localizations_nl.dart index b1f7be3bad..a5ce109cd0 100644 --- a/mobile/lib/l10n/generated/app_localizations_nl.dart +++ b/mobile/lib/l10n/generated/app_localizations_nl.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9925,8 +9926,7 @@ class AppLocalizationsNl extends AppLocalizations { locale: localeName, other: 'Dit blokkeert de $count accounts die deze badge nu hebben. Hun posts verschijnen niet meer in jouw feeds en ze krijgen hiervan geen melding.', - one: - 'Dit blokkeert het account dat deze badge nu heeft. Hun posts verschijnen niet meer in jouw feeds en ze krijgen hiervan geen melding.', + one: 'Dit blokkeert het account dat deze badge nu heeft. Hun posts verschijnen niet meer in jouw feeds en ze krijgen hiervan geen melding.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_pl.dart b/mobile/lib/l10n/generated/app_localizations_pl.dart index db1eb82ab6..5fa161e0b8 100644 --- a/mobile/lib/l10n/generated/app_localizations_pl.dart +++ b/mobile/lib/l10n/generated/app_localizations_pl.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -10070,8 +10071,7 @@ class AppLocalizationsPl extends AppLocalizations { 'To zablokuje $count kont, które mają teraz tę odznakę. Ich posty nie będą pojawiać się w twoich kanałach i nie zostaną powiadomione.', few: 'To zablokuje $count konta, które mają teraz tę odznakę. Ich posty nie będą pojawiać się w twoich kanałach i nie zostaną powiadomione.', - one: - 'To zablokuje konto, które ma teraz tę odznakę. Jego posty nie będą pojawiać się w twoich kanałach i nie zostanie powiadomione.', + one: 'To zablokuje konto, które ma teraz tę odznakę. Jego posty nie będą pojawiać się w twoich kanałach i nie zostanie powiadomione.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_pt.dart b/mobile/lib/l10n/generated/app_localizations_pt.dart index 4b94349289..c3441db94e 100644 --- a/mobile/lib/l10n/generated/app_localizations_pt.dart +++ b/mobile/lib/l10n/generated/app_localizations_pt.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9947,8 +9948,7 @@ class AppLocalizationsPt extends AppLocalizations { locale: localeName, other: 'Isso bloqueia as $count contas que estão usando este selo agora. Os posts delas não vão aparecer no seu feed e elas não serão notificadas.', - one: - 'Isso bloqueia a conta que está usando este selo agora. Os posts dessa pessoa não vão aparecer no seu feed e ela não será notificada.', + one: 'Isso bloqueia a conta que está usando este selo agora. Os posts dessa pessoa não vão aparecer no seu feed e ela não será notificada.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_ro.dart b/mobile/lib/l10n/generated/app_localizations_ro.dart index bf9b87597b..d64fecdc3f 100644 --- a/mobile/lib/l10n/generated/app_localizations_ro.dart +++ b/mobile/lib/l10n/generated/app_localizations_ro.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -10094,8 +10095,7 @@ class AppLocalizationsRo extends AppLocalizations { 'Asta blochează cele $count de conturi care poartă acum această insignă. Postările lor nu vor mai apărea în feedurile tale și nu vor fi notificate.', few: 'Asta blochează cele $count conturi care poartă acum această insignă. Postările lor nu vor mai apărea în feedurile tale și nu vor fi notificate.', - one: - 'Asta blochează contul care poartă acum această insignă. Postările lui nu vor mai apărea în feedurile tale și nu va fi notificat.', + one: 'Asta blochează contul care poartă acum această insignă. Postările lui nu vor mai apărea în feedurile tale și nu va fi notificat.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_sv.dart b/mobile/lib/l10n/generated/app_localizations_sv.dart index e5c7af0a60..b720f1e901 100644 --- a/mobile/lib/l10n/generated/app_localizations_sv.dart +++ b/mobile/lib/l10n/generated/app_localizations_sv.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9876,8 +9877,7 @@ class AppLocalizationsSv extends AppLocalizations { locale: localeName, other: 'Det här blockerar de $count konton som har den här badgen just nu. Deras inlägg visas inte i dina flöden och de meddelas inte.', - one: - 'Det här blockerar kontot som har den här badgen just nu. Deras inlägg visas inte i dina flöden och de meddelas inte.', + one: 'Det här blockerar kontot som har den här badgen just nu. Deras inlägg visas inte i dina flöden och de meddelas inte.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_te.dart b/mobile/lib/l10n/generated/app_localizations_te.dart index 8ba112b3e1..46a5ed419a 100644 --- a/mobile/lib/l10n/generated/app_localizations_te.dart +++ b/mobile/lib/l10n/generated/app_localizations_te.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -10166,8 +10167,7 @@ class AppLocalizationsTe extends AppLocalizations { locale: localeName, other: 'ఇది బ్లాక్ చేస్తుంది $countఖాతాలు ప్రస్తుతం ఈ బ్యాడ్జ్‌ను క్లెయిమ్ చేస్తున్నాయి. వారి పోస్ట్‌లు మీ ఫీడ్‌లను వదిలివేస్తాయి మరియు వారికి తెలియజేయబడదు.', - one: - 'ఇది ప్రస్తుతం ఈ బ్యాడ్జ్‌ను క్లెయిమ్ చేస్తున్న ఖాతాను బ్లాక్ చేస్తుంది. వారి పోస్ట్‌లు మీ ఫీడ్‌లను వదిలివేస్తాయి మరియు వారికి తెలియజేయబడదు.', + one: 'ఇది ప్రస్తుతం ఈ బ్యాడ్జ్‌ను క్లెయిమ్ చేస్తున్న ఖాతాను బ్లాక్ చేస్తుంది. వారి పోస్ట్‌లు మీ ఫీడ్‌లను వదిలివేస్తాయి మరియు వారికి తెలియజేయబడదు.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_tr.dart b/mobile/lib/l10n/generated/app_localizations_tr.dart index 60197c80d0..107e23d8a2 100644 --- a/mobile/lib/l10n/generated/app_localizations_tr.dart +++ b/mobile/lib/l10n/generated/app_localizations_tr.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9790,8 +9791,7 @@ class AppLocalizationsTr extends AppLocalizations { locale: localeName, other: 'Bu, şu anda bu rozeti taşıyan $count hesabı engeller. Gönderileri akışında görünmez ve bu değişiklik hakkında bilgilendirilmezler.', - one: - 'Bu, şu anda bu rozeti taşıyan hesabı engeller. Gönderileri akışında görünmez ve bu değişiklik hakkında bilgilendirilmez.', + one: 'Bu, şu anda bu rozeti taşıyan hesabı engeller. Gönderileri akışında görünmez ve bu değişiklik hakkında bilgilendirilmez.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_ur.dart b/mobile/lib/l10n/generated/app_localizations_ur.dart index 0a1c0f7cd7..e3ae28d92e 100644 --- a/mobile/lib/l10n/generated/app_localizations_ur.dart +++ b/mobile/lib/l10n/generated/app_localizations_ur.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9870,8 +9871,7 @@ class AppLocalizationsUr extends AppLocalizations { locale: localeName, other: 'اس سے وہ $count اکاؤنٹس بلاک ہو جائیں گے جو ابھی یہ بیج رکھتے ہیں۔ ان کی پوسٹیں آپ کے فیڈز میں نظر نہیں آئیں گی اور انہیں اطلاع نہیں دی جائے گی۔', - one: - 'اس سے وہ اکاؤنٹ بلاک ہو جائے گا جو ابھی یہ بیج رکھتا ہے۔ ان کی پوسٹیں آپ کے فیڈز میں نظر نہیں آئیں گی اور انہیں اطلاع نہیں دی جائے گی۔', + one: 'اس سے وہ اکاؤنٹ بلاک ہو جائے گا جو ابھی یہ بیج رکھتا ہے۔ ان کی پوسٹیں آپ کے فیڈز میں نظر نہیں آئیں گی اور انہیں اطلاع نہیں دی جائے گی۔', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_vi.dart b/mobile/lib/l10n/generated/app_localizations_vi.dart index 4e0bd3e805..ca8597fa5d 100644 --- a/mobile/lib/l10n/generated/app_localizations_vi.dart +++ b/mobile/lib/l10n/generated/app_localizations_vi.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9832,8 +9833,7 @@ class AppLocalizationsVi extends AppLocalizations { locale: localeName, other: 'Thao tác này chặn $count tài khoản đang gắn huy hiệu này. Bài đăng của họ sẽ không xuất hiện trong bảng tin của bạn và họ sẽ không được thông báo.', - one: - 'Thao tác này chặn tài khoản đang gắn huy hiệu này. Bài đăng của họ sẽ không xuất hiện trong bảng tin của bạn và họ sẽ không được thông báo.', + one: 'Thao tác này chặn tài khoản đang gắn huy hiệu này. Bài đăng của họ sẽ không xuất hiện trong bảng tin của bạn và họ sẽ không được thông báo.', ); return '$_temp0'; } diff --git a/mobile/lib/l10n/generated/app_localizations_zh.dart b/mobile/lib/l10n/generated/app_localizations_zh.dart index 0064eb6720..4d30565d80 100644 --- a/mobile/lib/l10n/generated/app_localizations_zh.dart +++ b/mobile/lib/l10n/generated/app_localizations_zh.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint diff --git a/mobile/lib/models/auth_service_callbacks.dart b/mobile/lib/models/auth_service_callbacks.dart new file mode 100644 index 0000000000..b35c8f0b68 --- /dev/null +++ b/mobile/lib/models/auth_service_callbacks.dart @@ -0,0 +1,28 @@ +// ABOUTME: Callback and factory port typedefs injected into AuthService. +// ABOUTME: Re-exported by auth_service.dart, like its sibling auth contracts. + +import 'package:nostr_sdk/nostr_sdk.dart'; + +/// Callback to pre-fetch following list from REST API before auth state is set. +/// +/// Called during login setup to populate SharedPreferences cache so the +/// router redirect has accurate following data before it fires synchronously. +typedef PreFetchFollowingCallback = Future Function(String pubkeyHex); + +/// Port for launching the NIP-46 bunker auth URL in an external browser. +/// +/// Returns whether the URL could be launched. Wired in the app layer to +/// url_launcher so this service carries no Flutter-plugin dependency for the +/// launch; when unset (tests), the auth URL is logged as unlaunchable instead +/// of hitting a platform channel. +typedef AuthUrlLauncher = Future Function(Uri url); + +/// Callback invoked before AuthService clears the outgoing session identity. +typedef BeforeSessionTeardownCallback = Future Function(); + +/// Factory for NIP-46 remote signers. Injected in tests so startup restore can +/// exercise unreachable signer behavior without opening relay sockets. +typedef RemoteSignerFactory = NostrRemoteSigner Function( + int relayMode, + NostrRemoteSignerInfo info, +); diff --git a/mobile/lib/models/curated_list_callbacks.dart b/mobile/lib/models/curated_list_callbacks.dart new file mode 100644 index 0000000000..503f258ba2 --- /dev/null +++ b/mobile/lib/models/curated_list_callbacks.dart @@ -0,0 +1,13 @@ +// ABOUTME: Subscription callback typedefs used by CuratedListService. +// ABOUTME: Re-exported by curated_list_service.dart for its existing callers. + +/// Callback type for list subscription events +/// Called with listId and the video IDs in that list +typedef OnListSubscribedCallback = Future Function( + String listId, + List videoIds, +); + +/// Callback type for list unsubscription events +/// Called with listId when a list is unsubscribed +typedef OnListUnsubscribedCallback = void Function(String listId); diff --git a/mobile/lib/models/pending_upload.dart b/mobile/lib/models/pending_upload.dart index 0bfeb9854f..47c85a5cdb 100644 --- a/mobile/lib/models/pending_upload.dart +++ b/mobile/lib/models/pending_upload.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:math' as math; + import 'package:blossom_upload_service/blossom_upload_service.dart'; import 'package:hive_ce/hive.dart'; import 'package:meta/meta.dart'; diff --git a/mobile/lib/models/subtitle_editor/timeline_frame.dart b/mobile/lib/models/subtitle_editor/timeline_frame.dart index 7ecf1ec201..c559760049 100644 --- a/mobile/lib/models/subtitle_editor/timeline_frame.dart +++ b/mobile/lib/models/subtitle_editor/timeline_frame.dart @@ -23,10 +23,9 @@ class TimelineFrame extends Equatable { /// Each event is the full set extracted so far. Injected as a function rather /// than the service itself so the widgets that render frames stay out of the /// service layer. -typedef TimelineFrameLoader = - Stream> Function({ - required String videoUrl, - required String videoId, - required Duration duration, - required double devicePixelRatio, - }); +typedef TimelineFrameLoader = Stream> Function({ + required String videoUrl, + required String videoId, + required Duration duration, + required double devicePixelRatio, +}); diff --git a/mobile/lib/nostr/transport/in_memory_transport.dart b/mobile/lib/nostr/transport/in_memory_transport.dart index 6c457d9939..addf1f6fc1 100644 --- a/mobile/lib/nostr/transport/in_memory_transport.dart +++ b/mobile/lib/nostr/transport/in_memory_transport.dart @@ -2,6 +2,7 @@ // ABOUTME: Allows injecting relay messages without network IO import 'dart:async'; + import 'package:openvine/nostr/transport/nostr_transport.dart'; /// In-memory transport for testing - no network IO diff --git a/mobile/lib/nostr/transport/nostr_fixture_pump.dart b/mobile/lib/nostr/transport/nostr_fixture_pump.dart index 9c66ffb815..4d3c48738b 100644 --- a/mobile/lib/nostr/transport/nostr_fixture_pump.dart +++ b/mobile/lib/nostr/transport/nostr_fixture_pump.dart @@ -2,6 +2,7 @@ // ABOUTME: Enables deterministic testing with pre-recorded relay data import 'dart:convert'; + import 'package:openvine/nostr/transport/in_memory_transport.dart'; /// Pumps NDJSON fixture data into an InMemoryNostrTransport diff --git a/mobile/lib/notifications/notification_tap_router.dart b/mobile/lib/notifications/notification_tap_router.dart index a94318fcbd..2abf75d39f 100644 --- a/mobile/lib/notifications/notification_tap_router.dart +++ b/mobile/lib/notifications/notification_tap_router.dart @@ -46,13 +46,12 @@ bool shouldRenderLocalPushNotification(RemoteMessage message) { } typedef BackgroundFirebaseInitializer = Future Function(); -typedef BackgroundLocalPushRenderer = - Future Function({ - required int id, - required String? title, - required String body, - required Map data, - }); +typedef BackgroundLocalPushRenderer = Future Function({ + required int id, + required String? title, + required String body, + required Map data, +}); Future handleFirebaseMessagingBackgroundMessage( RemoteMessage message, { BackgroundFirebaseInitializer initializeFirebase = diff --git a/mobile/lib/notifications/services/notification_refresh_coordinator.dart b/mobile/lib/notifications/services/notification_refresh_coordinator.dart index 1f8f162f62..e859ef196d 100644 --- a/mobile/lib/notifications/services/notification_refresh_coordinator.dart +++ b/mobile/lib/notifications/services/notification_refresh_coordinator.dart @@ -23,8 +23,11 @@ enum NotificationRefreshReason { } /// Forwards an unexpected refresh failure to the crash reporter. -typedef NotificationRefreshErrorReporter = - void Function(Object error, StackTrace stackTrace, {String? reason}); +typedef NotificationRefreshErrorReporter = void Function( + Object error, + StackTrace stackTrace, { + String? reason, +}); /// Coalesces notification refresh calls so independent liveness triggers do /// not stampede Funnelcake. diff --git a/mobile/lib/observability/network/firebase_http_metric_recorder.dart b/mobile/lib/observability/network/firebase_http_metric_recorder.dart index 7d2828516c..5311ff6a72 100644 --- a/mobile/lib/observability/network/firebase_http_metric_recorder.dart +++ b/mobile/lib/observability/network/firebase_http_metric_recorder.dart @@ -9,8 +9,10 @@ import 'package:unified_logger/unified_logger.dart'; /// Creates the underlying Firebase metric. Injected so the recorder can be /// tested without a Firebase app. -typedef HttpMetricFactory = - HttpMetric Function(String url, HttpMethod httpMethod); +typedef HttpMetricFactory = HttpMetric Function( + String url, + HttpMethod httpMethod, +); const String _logName = 'HttpMetricRecorder'; diff --git a/mobile/lib/providers/crossposting_providers.dart b/mobile/lib/providers/crossposting_providers.dart index 948902181b..e81b3fc31b 100644 --- a/mobile/lib/providers/crossposting_providers.dart +++ b/mobile/lib/providers/crossposting_providers.dart @@ -21,10 +21,9 @@ final crosspostingEligibleProvider = Provider((ref) { authService.isRegistered; }); -typedef CrosspostingApiClientFactory = - CrosspostingApiClient Function( - CrosspostingAccessTokenReader accessTokenReader, - ); +typedef CrosspostingApiClientFactory = CrosspostingApiClient Function( + CrosspostingAccessTokenReader accessTokenReader, +); final crosspostingApiClientFactoryProvider = Provider((ref) { diff --git a/mobile/lib/providers/developer_mode_tap_provider.dart b/mobile/lib/providers/developer_mode_tap_provider.dart index 0775073858..31f97fbe56 100644 --- a/mobile/lib/providers/developer_mode_tap_provider.dart +++ b/mobile/lib/providers/developer_mode_tap_provider.dart @@ -2,6 +2,7 @@ // ABOUTME: Tracks taps to unlock developer mode (7 taps) import 'dart:async'; + import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'developer_mode_tap_provider.g.dart'; diff --git a/mobile/lib/providers/image_crop_launcher_provider.dart b/mobile/lib/providers/image_crop_launcher_provider.dart index 24db9f9005..b94067cb59 100644 --- a/mobile/lib/providers/image_crop_launcher_provider.dart +++ b/mobile/lib/providers/image_crop_launcher_provider.dart @@ -7,13 +7,12 @@ import 'package:openvine/screens/image_crop_editor/image_crop_editor.dart'; /// Signature of the function that launches the crop editor and resolves to the /// cropped JPEG bytes (or `null` when cancelled). -typedef ImageCropLauncher = - Future Function( - BuildContext context, { - required ImageCropKind kind, - File? file, - Uint8List? bytes, - }); +typedef ImageCropLauncher = Future Function( + BuildContext context, { + required ImageCropKind kind, + File? file, + Uint8List? bytes, +}); /// Injectable seam for [showImageCropEditor]. /// diff --git a/mobile/lib/providers/minor_account_review_providers.dart b/mobile/lib/providers/minor_account_review_providers.dart index 62cbd10caf..c818f3f66c 100644 --- a/mobile/lib/providers/minor_account_review_providers.dart +++ b/mobile/lib/providers/minor_account_review_providers.dart @@ -14,13 +14,12 @@ import 'package:openvine/services/auth_service.dart'; import 'package:openvine/services/minor_account_review_override_service.dart'; import 'package:openvine/services/support_email_composer.dart'; -typedef MinorAccountReviewComposeEmail = - Future Function({ - required String toEmail, - required String subject, - required String body, - Rect? sharePositionOrigin, - }); +typedef MinorAccountReviewComposeEmail = Future Function({ + required String toEmail, + required String subject, + required String body, + Rect? sharePositionOrigin, +}); /// Support-email composer used by minor-account review screens. final minorAccountReviewSupportEmailComposerProvider = diff --git a/mobile/lib/providers/moderation_providers.dart b/mobile/lib/providers/moderation_providers.dart index bcf42d5002..ef6892f08e 100644 --- a/mobile/lib/providers/moderation_providers.dart +++ b/mobile/lib/providers/moderation_providers.dart @@ -2,6 +2,7 @@ // ABOUTME: Policy engine, host/age/content filters, NIP-32 labels, blocklist + sync bridge import 'dart:async'; + import 'package:content_blocklist_repository/content_blocklist_repository.dart'; import 'package:content_policy/content_policy.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/mobile/lib/providers/nostr_client_provider.dart b/mobile/lib/providers/nostr_client_provider.dart index 3b356037c9..9200c67ad6 100644 --- a/mobile/lib/providers/nostr_client_provider.dart +++ b/mobile/lib/providers/nostr_client_provider.dart @@ -110,13 +110,12 @@ final nip89ClientTagEnabledProvider = FutureProvider((ref) async { /// delegates to [NostrServiceFactory.create]. Tests override /// [nostrClientFactoryProvider] to inject fake clients and observe the /// arguments NostrService passes. -typedef NostrClientFactory = - NostrClient Function({ - NostrSigner? signer, - RelayStatisticsService? statisticsService, - EnvironmentConfig? environmentConfig, - AppDbClient? dbClient, - }); +typedef NostrClientFactory = NostrClient Function({ + NostrSigner? signer, + RelayStatisticsService? statisticsService, + EnvironmentConfig? environmentConfig, + AppDbClient? dbClient, +}); typedef NostrInitRetryDelay = Duration Function(int attempt); diff --git a/mobile/lib/providers/swap_account.dart b/mobile/lib/providers/swap_account.dart index 264ae161df..9ff435014d 100644 --- a/mobile/lib/providers/swap_account.dart +++ b/mobile/lib/providers/swap_account.dart @@ -24,8 +24,10 @@ import 'package:unified_logger/unified_logger.dart'; /// `AuthService` instance, so the sign-in's global-session mutation lands on /// the new instance, never the live one. That is what lets the in-place swap /// skip the side-effect-free `activate()` refactor (design §8.2 correction). -typedef AccountSignIn = - Future Function(ProviderContainer container, KnownAccount account); +typedef AccountSignIn = Future Function( + ProviderContainer container, + KnownAccount account, +); const _accountSwitchPushCleanupTimeout = Duration(seconds: 15); diff --git a/mobile/lib/repositories/sticker_repository.dart b/mobile/lib/repositories/sticker_repository.dart index cf8e3bbca9..185224dd3a 100644 --- a/mobile/lib/repositories/sticker_repository.dart +++ b/mobile/lib/repositories/sticker_repository.dart @@ -65,11 +65,9 @@ class StickerRepository { } Future> _loadStrings(String localeCode) async { - final raw = - json.decode( - await _bundle.loadString('$_i18nDirectory/$localeCode.json'), - ) - as Map; + final raw = json.decode( + await _bundle.loadString('$_i18nDirectory/$localeCode.json'), + ) as Map; return raw.map((key, value) => MapEntry(key, value as String)); } diff --git a/mobile/lib/screens/apps/nostr_app_sandbox_screen.dart b/mobile/lib/screens/apps/nostr_app_sandbox_screen.dart index 5a7cb279a2..0fa990a122 100644 --- a/mobile/lib/screens/apps/nostr_app_sandbox_screen.dart +++ b/mobile/lib/screens/apps/nostr_app_sandbox_screen.dart @@ -27,8 +27,9 @@ import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; -typedef SandboxViewBuilder = - Widget Function(void Function(Uri uri) onNavigationAttempt); +typedef SandboxViewBuilder = Widget Function( + void Function(Uri uri) onNavigationAttempt, +); typedef SandboxJavaScriptRunner = Future Function(String script); typedef SandboxImageMetadataStripper = Future Function(File imageFile); @@ -806,10 +807,10 @@ Future> sandboxAndroidFileSelector( if (file == null) { return const []; } - return _strippedFileUris([file], stripMetadata); + return await _strippedFileUris([file], stripMetadata); } final files = await picker.pickMultiImage(); - return _strippedFileUris(files, stripMetadata); + return await _strippedFileUris(files, stripMetadata); } final source = params.isCaptureEnabled @@ -819,7 +820,7 @@ Future> sandboxAndroidFileSelector( if (file == null) { return const []; } - return _strippedFileUris([file], stripMetadata); + return await _strippedFileUris([file], stripMetadata); } catch (error, stackTrace) { // Expected, user-driven paths land here too (e.g. denying the camera // permission throws PlatformException), so log at warning, not error. diff --git a/mobile/lib/screens/comments/widgets/comment_item.dart b/mobile/lib/screens/comments/widgets/comment_item.dart index d61d02a3e8..8699074186 100644 --- a/mobile/lib/screens/comments/widgets/comment_item.dart +++ b/mobile/lib/screens/comments/widgets/comment_item.dart @@ -523,15 +523,13 @@ class _ActionsRow extends StatelessWidget { // reliably works. A follow-up scroll to the inserted reply itself // is driven by CommentsList once the placeholder appears. final reduceMotion = MediaQuery.of(context).disableAnimations; - unawaited( - Scrollable.ensureVisible( - context, - alignment: 0.35, - duration: reduceMotion - ? Duration.zero - : const Duration(milliseconds: 300), - curve: Curves.easeOut, - ), + Scrollable.ensureVisible( + context, + alignment: 0.35, + duration: reduceMotion + ? Duration.zero + : const Duration(milliseconds: 300), + curve: Curves.easeOut, ); context.read().add( CommentReplyToggled(commentId), diff --git a/mobile/lib/screens/comments/widgets/comments_list.dart b/mobile/lib/screens/comments/widgets/comments_list.dart index 782ead3b92..05bd10ff3c 100644 --- a/mobile/lib/screens/comments/widgets/comments_list.dart +++ b/mobile/lib/screens/comments/widgets/comments_list.dart @@ -1,8 +1,6 @@ // ABOUTME: Comments list widget with loading, error, and empty states // ABOUTME: Renders comments in a flat list using CommentItem widget -import 'dart:async'; - import 'package:divine_ui/divine_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -64,15 +62,13 @@ class _CommentsListState extends State { final targetContext = _scrollTargetKey.currentContext; if (targetContext != null) { final reduceMotion = MediaQuery.of(context).disableAnimations; - unawaited( - Scrollable.ensureVisible( - targetContext, - alignment: 0.3, - duration: reduceMotion - ? Duration.zero - : const Duration(milliseconds: 300), - curve: Curves.easeOut, - ), + Scrollable.ensureVisible( + targetContext, + alignment: 0.3, + duration: reduceMotion + ? Duration.zero + : const Duration(milliseconds: 300), + curve: Curves.easeOut, ); } context.read().add(const CommentsScrollHandled()); diff --git a/mobile/lib/screens/feed/pooled_age_restricted_retry.dart b/mobile/lib/screens/feed/pooled_age_restricted_retry.dart index 991e11824c..a667a53a79 100644 --- a/mobile/lib/screens/feed/pooled_age_restricted_retry.dart +++ b/mobile/lib/screens/feed/pooled_age_restricted_retry.dart @@ -21,8 +21,10 @@ import 'package:unified_logger/unified_logger.dart'; const _logName = 'PooledAgeRestrictedRetry'; -typedef PooledAgeRestrictedSha256Resolver = - String? Function({String? explicitSha256, String? videoUrl}); +typedef PooledAgeRestrictedSha256Resolver = String? Function({ + String? explicitSha256, + String? videoUrl, +}); /// Outcome of a viewer-auth playback retry. /// @@ -33,8 +35,9 @@ typedef PooledAgeRestrictedSha256Resolver = typedef PooledRetryOutcome = ({bool succeeded, VideoErrorType? errorType}); /// Reloads playback for the retried item with signed viewer-auth headers. -typedef PooledRetryPlayback = - FutureOr Function(Map httpHeaders); +typedef PooledRetryPlayback = FutureOr Function( + Map httpHeaders, +); /// Verifies access to an age-restricted pooled video, then retries playback /// with viewer auth headers on the active pooled controller item. diff --git a/mobile/lib/screens/feed/pooled_fullscreen_video_feed_screen.dart b/mobile/lib/screens/feed/pooled_fullscreen_video_feed_screen.dart index 88ef368cfe..c1f76a5c87 100644 --- a/mobile/lib/screens/feed/pooled_fullscreen_video_feed_screen.dart +++ b/mobile/lib/screens/feed/pooled_fullscreen_video_feed_screen.dart @@ -703,7 +703,7 @@ class _FullscreenFeedContentState extends ConsumerState onBack(); return; } - unawaited(Navigator.of(context).maybePop()); + Navigator.of(context).maybePop(); }, ), // Show an Undo snackbar after a feed-tuning swipe commits. diff --git a/mobile/lib/screens/profile_setup/view/profile_setup_view.dart b/mobile/lib/screens/profile_setup/view/profile_setup_view.dart index d866a02d1e..eebfafdee5 100644 --- a/mobile/lib/screens/profile_setup/view/profile_setup_view.dart +++ b/mobile/lib/screens/profile_setup/view/profile_setup_view.dart @@ -123,8 +123,7 @@ class _ProfileSetupScreenViewState FocusManager.instance.primaryFocus?.unfocus(); }, child: SafeArea( - bottom: - false, // Don't add bottom padding - let content extend to bottom + bottom: false, // Don't add bottom padding - let content extend to bottom // Rounds the body's top corners the way the app bar's // `radius 96` pieces do. Outside the scroll view on purpose: // the corners belong to the viewport edge, so they stay put diff --git a/mobile/lib/screens/video_detail_screen.dart b/mobile/lib/screens/video_detail_screen.dart index 9d123c8f7d..14fd9bc409 100644 --- a/mobile/lib/screens/video_detail_screen.dart +++ b/mobile/lib/screens/video_detail_screen.dart @@ -2,6 +2,7 @@ // ABOUTME: Fetches video from Nostr and displays it in full-screen player import 'dart:async'; + import 'package:divine_ui/divine_ui.dart'; import 'package:feed_repository/feed_repository.dart'; import 'package:flutter/foundation.dart'; diff --git a/mobile/lib/services/account_status_api_client.dart b/mobile/lib/services/account_status_api_client.dart index b37650e008..c9ba315ca9 100644 --- a/mobile/lib/services/account_status_api_client.dart +++ b/mobile/lib/services/account_status_api_client.dart @@ -27,8 +27,10 @@ class AccountStatusApiException implements Exception { String toString() => 'AccountStatusApiException($kind): $message'; } -typedef AccountStatusAuthHeaderProvider = - Future Function({required String url, required HttpMethod method}); +typedef AccountStatusAuthHeaderProvider = Future Function({ + required String url, + required HttpMethod method, +}); class AccountStatusApiClient { AccountStatusApiClient({ diff --git a/mobile/lib/services/auth/known_accounts_registry.dart b/mobile/lib/services/auth/known_accounts_registry.dart index 665013be46..15ec823fc2 100644 --- a/mobile/lib/services/auth/known_accounts_registry.dart +++ b/mobile/lib/services/auth/known_accounts_registry.dart @@ -89,7 +89,7 @@ class KnownAccountsRegistry { // null → key never written → run one-time migration // empty → key was written but all accounts removed → no migration if (raw == null) { - return _migrateLegacyAccount(prefs); + return await _migrateLegacyAccount(prefs); } if (raw.isEmpty) return []; diff --git a/mobile/lib/services/auth/nostr_connect_coordinator.dart b/mobile/lib/services/auth/nostr_connect_coordinator.dart index 70d41aaa60..854a6d90ff 100644 --- a/mobile/lib/services/auth/nostr_connect_coordinator.dart +++ b/mobile/lib/services/auth/nostr_connect_coordinator.dart @@ -13,23 +13,24 @@ import 'package:unified_logger/unified_logger.dart'; /// Builds a [NostrConnectSession] for a set of [relays]. Injectable so tests /// can supply a fake session that completes `waitForConnection` without opening /// real relay sockets (closes the deferred nostrconnect happy-path gap #5713). -typedef NostrConnectSessionFactory = - NostrConnectSession Function(List relays); +typedef NostrConnectSessionFactory = NostrConnectSession Function( + List relays, +); /// Applies a successful nostrconnect connection on the facade: builds the /// bunker signer from [result], persists its info, and sets up the user /// session. Returns the [AuthResult] to surface to the caller. -typedef NostrConnectApply = - Future Function(NostrConnectResult result); +typedef NostrConnectApply = Future Function( + NostrConnectResult result, +); /// Reports an error to Crashlytics — mirrors `AuthService._reportAuthError`. -typedef NostrConnectErrorReporter = - void Function( - Object error, - StackTrace stack, { - required String reason, - required String logMessage, - }); +typedef NostrConnectErrorReporter = void Function( + Object error, + StackTrace stack, { + required String reason, + required String logMessage, +}); /// Coordinates the client-initiated `nostrconnect://` connection flow. /// @@ -209,8 +210,7 @@ class NostrConnectCoordinator { ), StackTrace.current, reason: 'NostrConnect.noExpectedSecret', - logMessage: - 'nostrconnect invariant violated: no expected secret to validate', + logMessage: 'nostrconnect invariant violated: no expected secret to validate', ); } return AuthResult.nostrConnectFailure(reason); diff --git a/mobile/lib/services/auth/relay_discovery_orchestrator.dart b/mobile/lib/services/auth/relay_discovery_orchestrator.dart index 7fb9b91438..c227171980 100644 --- a/mobile/lib/services/auth/relay_discovery_orchestrator.dart +++ b/mobile/lib/services/auth/relay_discovery_orchestrator.dart @@ -13,8 +13,10 @@ import 'package:unified_logger/unified_logger.dart'; /// Callback invoked when NIP-65 relay discovery completes with a non-empty /// list. NostrService uses this to add discovered relays to the current /// client without blocking app startup. -typedef UserRelaysDiscoveredCallback = - void Function(String pubkey, List relayUrls); +typedef UserRelaysDiscoveredCallback = void Function( + String pubkey, + List relayUrls, +); /// Callback invoked when AuthService wants to publish a bootstrap kind:10002 /// relay list on behalf of the user (because indexer discovery returned empty). @@ -22,8 +24,10 @@ typedef UserRelaysDiscoveredCallback = /// The event is already signed. The implementer publishes it through the /// active [NostrClient] to [targetRelays] and reports success/failure via the /// returned future. -typedef BootstrapRelayListCallback = - Future Function(Event event, List targetRelays); +typedef BootstrapRelayListCallback = Future Function( + Event event, + List targetRelays, +); /// SharedPreferences key prefix for the per-pubkey one-shot flag that records /// whether we have already published a bootstrap kind:10002 on this device. diff --git a/mobile/lib/services/auth/signer_factory.dart b/mobile/lib/services/auth/signer_factory.dart index 8d06d4e04f..cd2cc6afe1 100644 --- a/mobile/lib/services/auth/signer_factory.dart +++ b/mobile/lib/services/auth/signer_factory.dart @@ -22,13 +22,12 @@ import 'package:unified_logger/unified_logger.dart'; /// tear-off of that funnel in the app layer, which keeps the Crashlytics /// `auth_source` custom key reading live state. Implementations MUST NOT /// throw. -typedef AuthCrashReporter = - void Function( - Object error, - StackTrace stackTrace, { - required String reason, - required String logMessage, - }); +typedef AuthCrashReporter = void Function( + Object error, + StackTrace stackTrace, { + required String reason, + required String logMessage, +}); /// Isolate worker for [SignerFactory.createAndSignEvent]'s remote-signature /// check. diff --git a/mobile/lib/services/auth_service.dart b/mobile/lib/services/auth_service.dart index c7cf30f421..835c4ec99c 100644 --- a/mobile/lib/services/auth_service.dart +++ b/mobile/lib/services/auth_service.dart @@ -19,6 +19,7 @@ import 'package:openvine/constants/terms_acceptance_keys.dart'; import 'package:openvine/models/account_restore_failed_exception.dart'; import 'package:openvine/models/auth_result.dart'; import 'package:openvine/models/auth_rpc_capability.dart'; +import 'package:openvine/models/auth_service_callbacks.dart'; import 'package:openvine/models/auth_state.dart'; import 'package:openvine/models/auth_user_profile.dart'; import 'package:openvine/models/authentication_source.dart'; @@ -48,6 +49,7 @@ import 'package:unified_logger/unified_logger.dart'; // without a facade import cycle; external consumers keep importing it here. export 'package:openvine/models/account_restore_failed_exception.dart'; export 'package:openvine/models/auth_result.dart'; +export 'package:openvine/models/auth_service_callbacks.dart'; export 'package:openvine/models/auth_state.dart'; export 'package:openvine/models/auth_user_profile.dart'; export 'package:openvine/models/authentication_source.dart'; @@ -84,28 +86,6 @@ enum AccountDeletionReadiness { requiresReauthentication, } -/// Callback to pre-fetch following list from REST API before auth state is set. -/// -/// Called during login setup to populate SharedPreferences cache so the -/// router redirect has accurate following data before it fires synchronously. -typedef PreFetchFollowingCallback = Future Function(String pubkeyHex); - -/// Port for launching the NIP-46 bunker auth URL in an external browser. -/// -/// Returns whether the URL could be launched. Wired in the app layer to -/// url_launcher so this service carries no Flutter-plugin dependency for the -/// launch; when unset (tests), the auth URL is logged as unlaunchable instead -/// of hitting a platform channel. -typedef AuthUrlLauncher = Future Function(Uri url); - -/// Callback invoked before AuthService clears the outgoing session identity. -typedef BeforeSessionTeardownCallback = Future Function(); - -/// Factory for NIP-46 remote signers. Injected in tests so startup restore can -/// exercise unreachable signer behavior without opening relay sockets. -typedef RemoteSignerFactory = - NostrRemoteSigner Function(int relayMode, NostrRemoteSignerInfo info); - /// Total time budget for pre-teardown callbacks during sign-out. const _kBeforeSessionTeardownTimeout = Duration(seconds: 5); @@ -2422,7 +2402,7 @@ class AuthService implements BackgroundAwareService, BlockListSigner { try { final privateKeyHex = await Nip49.decode(ncryptsec, password); - return importFromHex(privateKeyHex); + return await importFromHex(privateKeyHex); } on Nip49Exception { _setAuthState(AuthState.unauthenticated); return AuthResult.incorrectPassword(); @@ -3597,7 +3577,7 @@ class AuthService implements BackgroundAwareService, BlockListSigner { // Falls back to storage read if the container isn't loaded yet. final container = _currentKeyContainer; if (container != null && container.hasPrivateKey) { - return container.withNsec((nsec) => nsec); + return await container.withNsec((nsec) => nsec); } if (authenticationSource == AuthenticationSource.divineOAuth) { diff --git a/mobile/lib/services/author_video_buckets.dart b/mobile/lib/services/author_video_buckets.dart index 9c7aba1e70..a198586fa2 100644 --- a/mobile/lib/services/author_video_buckets.dart +++ b/mobile/lib/services/author_video_buckets.dart @@ -4,8 +4,10 @@ import 'package:models/models.dart'; /// Combines the cached and the incoming version of the same video. -typedef VideoMerge = - VideoEvent Function(VideoEvent existing, VideoEvent updated); +typedef VideoMerge = VideoEvent Function( + VideoEvent existing, + VideoEvent updated, +); /// Reports how many videos [AuthorVideoBuckets.removeWhere] dropped from one /// author's bucket. diff --git a/mobile/lib/services/background_activity_manager.dart b/mobile/lib/services/background_activity_manager.dart index 74227ec5ec..beee875ec1 100644 --- a/mobile/lib/services/background_activity_manager.dart +++ b/mobile/lib/services/background_activity_manager.dart @@ -2,6 +2,7 @@ // ABOUTME: Suspends non-critical services when app goes to background to conserve resources import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:unified_logger/unified_logger.dart'; diff --git a/mobile/lib/services/bug_report_service.dart b/mobile/lib/services/bug_report_service.dart index 6b531ea7ab..3192db2bba 100644 --- a/mobile/lib/services/bug_report_service.dart +++ b/mobile/lib/services/bug_report_service.dart @@ -26,11 +26,10 @@ import 'package:unified_logger/unified_logger.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:uuid/uuid.dart'; -typedef LogExportHeaderBuilder = - String Function({ - required int exportedCount, - required int omittedCount, - }); +typedef LogExportHeaderBuilder = String Function({ + required int exportedCount, + required int omittedCount, +}); @immutable class LogExportBody { @@ -394,9 +393,13 @@ class BugReportService { return _exportLogsWeb(content, fileName, export.exportedCount); } if (_isDesktop) { - return _exportLogsDesktop(content, fileName, export.exportedCount); + return await _exportLogsDesktop( + content, + fileName, + export.exportedCount, + ); } - return _exportLogsNative( + return await _exportLogsNative( content, fileName, export.exportedCount, diff --git a/mobile/lib/services/c2pa_identity_manifest_service.dart b/mobile/lib/services/c2pa_identity_manifest_service.dart index 3971301430..8dd079ac8f 100644 --- a/mobile/lib/services/c2pa_identity_manifest_service.dart +++ b/mobile/lib/services/c2pa_identity_manifest_service.dart @@ -70,9 +70,9 @@ class C2paIdentityManifestService { if (creatorBindingAssertion != null) CustomAssertion( label: creatorBindingAssertion.assertionLabel, - data: - jsonDecode(creatorBindingAssertion.payloadJson) - as Map, + data: jsonDecode( + creatorBindingAssertion.payloadJson, + ) as Map, ), if (cawgIdentityAssertion != null && !requiresAdvancedEmbedding) CustomAssertion( diff --git a/mobile/lib/services/circuit_breaker_service.dart b/mobile/lib/services/circuit_breaker_service.dart index 3995f9bfe0..ef58012170 100644 --- a/mobile/lib/services/circuit_breaker_service.dart +++ b/mobile/lib/services/circuit_breaker_service.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:collection'; + import 'package:unified_logger/unified_logger.dart'; /// Circuit breaker states diff --git a/mobile/lib/services/classic_viner_seed_preload_service.dart b/mobile/lib/services/classic_viner_seed_preload_service.dart index aae6db5843..79e51c0221 100644 --- a/mobile/lib/services/classic_viner_seed_preload_service.dart +++ b/mobile/lib/services/classic_viner_seed_preload_service.dart @@ -9,12 +9,11 @@ import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:unified_logger/unified_logger.dart'; -typedef ClassicVinerAvatarCacheWriter = - Future Function({ - required String cacheKey, - required Uint8List bytes, - required String fileExtension, - }); +typedef ClassicVinerAvatarCacheWriter = Future Function({ + required String cacheKey, + required Uint8List bytes, + required String fileExtension, +}); class ClassicVinerSeedPreloadService { ClassicVinerSeedPreloadService({ diff --git a/mobile/lib/services/crash_reporting_service.dart b/mobile/lib/services/crash_reporting_service.dart index abde174662..fdaacfe078 100644 --- a/mobile/lib/services/crash_reporting_service.dart +++ b/mobile/lib/services/crash_reporting_service.dart @@ -2,6 +2,7 @@ // ABOUTME: Uses Firebase Crashlytics to capture and report crashes from TestFlight/production import 'dart:async'; + import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/foundation.dart'; import 'package:openvine/observability/crash_reporter.dart'; @@ -11,8 +12,9 @@ import 'package:unified_logger/unified_logger.dart'; /// One call against Crashlytics, held until [CrashReportingService.initialize] /// has decided whether Crashlytics exists. -typedef _CrashlyticsCall = - Future Function(FirebaseCrashlytics crashlytics); +typedef _CrashlyticsCall = Future Function( + FirebaseCrashlytics crashlytics, +); /// Whether Crashlytics can take a call right now. enum _Readiness { diff --git a/mobile/lib/services/curated_list_service.dart b/mobile/lib/services/curated_list_service.dart index 68f87fbaf0..d582183f72 100644 --- a/mobile/lib/services/curated_list_service.dart +++ b/mobile/lib/services/curated_list_service.dart @@ -17,20 +17,14 @@ import 'package:nostr_client/nostr_client.dart'; import 'package:nostr_sdk/event.dart'; import 'package:nostr_sdk/filter.dart'; import 'package:nostr_sdk/nip19/pubkey_for_logs.dart'; +import 'package:openvine/models/curated_list_callbacks.dart'; import 'package:openvine/services/auth_service.dart'; import 'package:openvine/services/curated_list_relay_gateway.dart'; import 'package:openvine/utils/curated_list_privacy.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:unified_logger/unified_logger.dart'; -/// Callback type for list subscription events -/// Called with listId and the video IDs in that list -typedef OnListSubscribedCallback = - Future Function(String listId, List videoIds); - -/// Callback type for list unsubscription events -/// Called with listId when a list is unsubscribed -typedef OnListUnsubscribedCallback = void Function(String listId); +export 'package:openvine/models/curated_list_callbacks.dart'; /// Service for managing NIP-51 curated lists. /// diff --git a/mobile/lib/services/firebase_initialization.dart b/mobile/lib/services/firebase_initialization.dart index c7e142fb13..f4feaa2a3b 100644 --- a/mobile/lib/services/firebase_initialization.dart +++ b/mobile/lib/services/firebase_initialization.dart @@ -5,8 +5,9 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:openvine/firebase_options.dart'; typedef FirebaseAppsProvider = List Function(); -typedef FirebaseInitializer = - Future Function({FirebaseOptions? options}); +typedef FirebaseInitializer = Future Function({ + FirebaseOptions? options, +}); /// Ensures the default Firebase app exists exactly once. /// diff --git a/mobile/lib/services/foreground_idle_warmup_coordinator.dart b/mobile/lib/services/foreground_idle_warmup_coordinator.dart index cf99f7be14..74375aa2b9 100644 --- a/mobile/lib/services/foreground_idle_warmup_coordinator.dart +++ b/mobile/lib/services/foreground_idle_warmup_coordinator.dart @@ -18,8 +18,9 @@ enum ForegroundIdleWarmupTrigger { } /// Function used by the scheduler to request a warmup pass. -typedef ForegroundIdleWarmupRequest = - Future Function(ForegroundIdleWarmupTrigger trigger); +typedef ForegroundIdleWarmupRequest = Future Function( + ForegroundIdleWarmupTrigger trigger, +); /// Starts delayed and periodic foreground-idle warmup requests. class ForegroundIdleWarmupScheduler { diff --git a/mobile/lib/services/gallery_save_service.dart b/mobile/lib/services/gallery_save_service.dart index c47084157c..2881f026c2 100644 --- a/mobile/lib/services/gallery_save_service.dart +++ b/mobile/lib/services/gallery_save_service.dart @@ -109,7 +109,7 @@ class GallerySaveService { if (permResult != null) { // Permission denied — on desktop, save to Downloads instead. if (_isDesktop) { - return _saveToDownloads(resolvedPath); + return await _saveToDownloads(resolvedPath); } return permResult; } diff --git a/mobile/lib/services/immediate_completion_helper.dart b/mobile/lib/services/immediate_completion_helper.dart index faffea3e4b..0e51d54220 100644 --- a/mobile/lib/services/immediate_completion_helper.dart +++ b/mobile/lib/services/immediate_completion_helper.dart @@ -2,6 +2,7 @@ // ABOUTME: Provides reusable methods for completing operations as soon as relevant data arrives import 'dart:async'; + import 'package:nostr_sdk/event.dart'; import 'package:openvine/constants/nip71_migration.dart'; import 'package:openvine/constants/nostr_event_kinds.dart'; diff --git a/mobile/lib/services/nip05_verification_service.dart b/mobile/lib/services/nip05_verification_service.dart index 900d435eb6..fc3fcb5506 100644 --- a/mobile/lib/services/nip05_verification_service.dart +++ b/mobile/lib/services/nip05_verification_service.dart @@ -2,6 +2,7 @@ // ABOUTME: Uses Drift SQLite for persistence and in-memory cache for fast access. import 'dart:async'; + import 'package:db_client/db_client.dart'; import 'package:flutter/foundation.dart'; import 'package:nostr_sdk/nip05/nip05_validor.dart'; diff --git a/mobile/lib/services/nip07_service.dart b/mobile/lib/services/nip07_service.dart index c0b6149955..c6e80087c3 100644 --- a/mobile/lib/services/nip07_service.dart +++ b/mobile/lib/services/nip07_service.dart @@ -2,6 +2,7 @@ // ABOUTME: Provides clean Dart interface for one-click Nostr login via browser extensions import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:nostr_sdk/nip19/pubkey_for_logs.dart'; import 'package:openvine/services/nip07_interop.dart' as nip07; diff --git a/mobile/lib/services/quick_actions_coordinator.dart b/mobile/lib/services/quick_actions_coordinator.dart index 71e7c9918d..036b81ce79 100644 --- a/mobile/lib/services/quick_actions_coordinator.dart +++ b/mobile/lib/services/quick_actions_coordinator.dart @@ -10,8 +10,11 @@ import 'package:unified_logger/unified_logger.dart'; const quickActionCameraType = 'camera'; const quickActionNotificationsType = 'notifications'; -typedef QuickActionErrorReporter = - Future Function(Object error, StackTrace stackTrace, String reason); +typedef QuickActionErrorReporter = Future Function( + Object error, + StackTrace stackTrace, + String reason, +); typedef QuickActionDelay = Future Function(); typedef QuickActionCallbackScheduler = void Function(void Function() callback); diff --git a/mobile/lib/services/repost_resolver.dart b/mobile/lib/services/repost_resolver.dart index a6de70e998..f6af778699 100644 --- a/mobile/lib/services/repost_resolver.dart +++ b/mobile/lib/services/repost_resolver.dart @@ -16,8 +16,10 @@ typedef RepostTagRefs = ({String? eventId, String? addressableId}); typedef AddressableIdParts = ({int kind, String pubkey, String dTag}); /// Callback to lookup cached videos by addressable reference -typedef VideoByAddressableLookup = - VideoEvent? Function(String pubkey, String dTag); +typedef VideoByAddressableLookup = VideoEvent? Function( + String pubkey, + String dTag, +); /// Callback to lookup cached videos by event ID typedef VideoByIdLookup = VideoEvent? Function(String eventId); diff --git a/mobile/lib/services/subtitle_timeline_thumbnail_service.dart b/mobile/lib/services/subtitle_timeline_thumbnail_service.dart index c9a7111fbb..03f2d7a7b0 100644 --- a/mobile/lib/services/subtitle_timeline_thumbnail_service.dart +++ b/mobile/lib/services/subtitle_timeline_thumbnail_service.dart @@ -12,8 +12,10 @@ import 'package:unified_logger/unified_logger.dart'; /// Downloads a video and hands back a local file, or `null` when it could not /// be fetched. -typedef TimelineVideoDownloader = - Future Function({required String url, required String cacheKey}); +typedef TimelineVideoDownloader = Future Function({ + required String url, + required String cacheKey, +}); /// Resolves the event URL into a single file that can be cached and decoded. typedef TimelineVideoUrlResolver = Future Function(String? videoUrl); diff --git a/mobile/lib/services/support_email_composer.dart b/mobile/lib/services/support_email_composer.dart index e70f5c5680..9d9e88463e 100644 --- a/mobile/lib/services/support_email_composer.dart +++ b/mobile/lib/services/support_email_composer.dart @@ -8,20 +8,18 @@ import 'package:url_launcher/url_launcher.dart'; typedef ExternalUriLauncher = Future Function(Uri uri); typedef AndroidChooserLauncher = Future Function(Uri uri, String title); -typedef ShareTextLauncher = - Future Function( - String text, { - String? subject, - Rect? sharePositionOrigin, - }); +typedef ShareTextLauncher = Future Function( + String text, { + String? subject, + Rect? sharePositionOrigin, +}); -typedef SupportEmailCompose = - Future Function({ - required String toEmail, - required String subject, - required String body, - Rect? sharePositionOrigin, - }); +typedef SupportEmailCompose = Future Function({ + required String toEmail, + required String subject, + required String body, + Rect? sharePositionOrigin, +}); String _buildFallbackShareText({ required String toEmail, diff --git a/mobile/lib/services/supporter_api_client.dart b/mobile/lib/services/supporter_api_client.dart index b6063c2130..2afa3af910 100644 --- a/mobile/lib/services/supporter_api_client.dart +++ b/mobile/lib/services/supporter_api_client.dart @@ -182,12 +182,11 @@ class SupporterPurchaseClaim { typedef SupporterAuthHeader = ({String authorizationHeader, String pubkey}); /// Provides NIP-98 authentication for an exact request. -typedef SupporterAuthHeaderProvider = - Future Function({ - required String url, - required HttpMethod method, - String? payload, - }); +typedef SupporterAuthHeaderProvider = Future Function({ + required String url, + required HttpMethod method, + String? payload, +}); /// NIP-98 authenticated client for the version-one supporter Worker API. class SupporterApiClient { diff --git a/mobile/lib/services/top_hashtags_service.dart b/mobile/lib/services/top_hashtags_service.dart index 24af3f17d9..d39e3fd653 100644 --- a/mobile/lib/services/top_hashtags_service.dart +++ b/mobile/lib/services/top_hashtags_service.dart @@ -2,6 +2,7 @@ // ABOUTME: Provides popular hashtag suggestions for discovery and exploration. import 'dart:convert'; + import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter/services.dart'; import 'package:unified_logger/unified_logger.dart'; diff --git a/mobile/lib/services/upload_manager.dart b/mobile/lib/services/upload_manager.dart index 49ab70408e..c4912b90d8 100644 --- a/mobile/lib/services/upload_manager.dart +++ b/mobile/lib/services/upload_manager.dart @@ -67,12 +67,11 @@ typedef ThumbnailLegResult = ({String? cdnUrl, String? blurhash}); /// /// Mirrors [VideoThumbnailService.extractThumbnail]; exists so [UploadManager] /// can be handed a stand-in that does not need a decodable video. -typedef ThumbnailExtractor = - Future Function({ - required String videoPath, - required Duration targetTimestamp, - required int quality, - }); +typedef ThumbnailExtractor = Future Function({ + required String videoPath, + required Duration targetTimestamp, + required int quality, +}); /// App-layer adapter forwarding the upload pipeline's [TransientRenderCleaner] /// port to [StopMotionRenderService]. diff --git a/mobile/lib/services/video_clip_import_service.dart b/mobile/lib/services/video_clip_import_service.dart index 9b1867cc1a..01139469c9 100644 --- a/mobile/lib/services/video_clip_import_service.dart +++ b/mobile/lib/services/video_clip_import_service.dart @@ -14,20 +14,20 @@ import 'package:pro_video_editor/pro_video_editor.dart'; import 'package:text_sanitizer/text_sanitizer.dart'; import 'package:unified_logger/unified_logger.dart'; -typedef VideoClipDownloader = - Future Function({required String url, required String cacheKey}); - -typedef VideoClipThumbnailExtractor = - Future Function({ - required String videoPath, - required Duration targetTimestamp, - }); - -typedef VideoClipLastFrameExtractor = - Future Function({ - required String videoPath, - required Duration videoDuration, - }); +typedef VideoClipDownloader = Future Function({ + required String url, + required String cacheKey, +}); + +typedef VideoClipThumbnailExtractor = Future Function({ + required String videoPath, + required Duration targetTimestamp, +}); + +typedef VideoClipLastFrameExtractor = Future Function({ + required String videoPath, + required Duration videoDuration, +}); typedef DocumentsPathProvider = Future Function(); typedef Clock = DateTime Function(); diff --git a/mobile/lib/services/video_editor/clip_thumbnail_manager.dart b/mobile/lib/services/video_editor/clip_thumbnail_manager.dart index 04d1a0d4c1..d59c163e67 100644 --- a/mobile/lib/services/video_editor/clip_thumbnail_manager.dart +++ b/mobile/lib/services/video_editor/clip_thumbnail_manager.dart @@ -9,16 +9,15 @@ import 'package:openvine/services/video_thumbnail_service.dart'; /// Signature of [VideoThumbnailService.generateStripThumbnails], injectable /// so tests can assert pause/resume behaviour on the produced subscriptions /// without touching the native extractor. -typedef StripThumbnailStreamFactory = - Stream> Function({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset, - List? priorityTimestamps, - }); +typedef StripThumbnailStreamFactory = Stream> Function({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset, + List? priorityTimestamps, +}); /// Manages thumbnail loading and cleanup for a set of clips. /// diff --git a/mobile/lib/services/video_event_publisher.dart b/mobile/lib/services/video_event_publisher.dart index 972111d49d..6b9b034d47 100644 --- a/mobile/lib/services/video_event_publisher.dart +++ b/mobile/lib/services/video_event_publisher.dart @@ -233,7 +233,7 @@ class VideoEventPublisher { try { return await checker(sound); - } catch (error, _) { + } catch (error) { Log.warning( 'Unable to verify selected audio reuse consent; blocking reuse: ' '$error', @@ -1816,8 +1816,7 @@ class VideoEventPublisher { } event = await _authService.createAndSignEvent( - kind: - NIP71VideoKinds.getPreferredAddressableKind(), // NIP-71 addressable short video + kind: NIP71VideoKinds.getPreferredAddressableKind(), // NIP-71 addressable short video content: content, tags: tags, ); diff --git a/mobile/lib/services/video_event_service.dart b/mobile/lib/services/video_event_service.dart index fd045a4852..824cedc612 100644 --- a/mobile/lib/services/video_event_service.dart +++ b/mobile/lib/services/video_event_service.dart @@ -4026,8 +4026,7 @@ class VideoEventService extends ChangeNotifier implements VideoEventCache { // Create filter without restrictive date constraints final filter = Filter( - kinds: - NIP71VideoKinds.getAllVideoKinds(), // NIP-71 video events + legacy support + kinds: NIP71VideoKinds.getAllVideoKinds(), // NIP-71 video events + legacy support authors: authors, // Use same authors as main subscription if available until: until, // Only use 'until' if we have existing events limit: limit, diff --git a/mobile/lib/services/video_publish/video_publish_service.dart b/mobile/lib/services/video_publish/video_publish_service.dart index 042d6b421e..467850b4ac 100644 --- a/mobile/lib/services/video_publish/video_publish_service.dart +++ b/mobile/lib/services/video_publish/video_publish_service.dart @@ -166,8 +166,10 @@ class CollaboratorInviteWarning extends Equatable { /// Callbacks for VideoPublishService to communicate state changes. /// This abstraction makes the service testable without Riverpod dependencies. typedef OnStateChanged = void Function(VideoPublishState state); -typedef OnProgressChanged = - void Function({required String draftId, required double progress}); +typedef OnProgressChanged = void Function({ + required String draftId, + required double progress, +}); class VideoPublishService { VideoPublishService({ @@ -776,7 +778,7 @@ class VideoPublishService { if (File(path).existsSync()) return path; } if (draft.clips.isNotEmpty) { - return draft.clips.first.requireVideo.safeFilePath(); + return await draft.clips.first.requireVideo.safeFilePath(); } } catch (e) { Log.warning('⚠️ Could not resolve video path: $e', category: .video); diff --git a/mobile/lib/services/video_thumbnail_service.dart b/mobile/lib/services/video_thumbnail_service.dart index 0b68c8f58a..ff888bcb40 100644 --- a/mobile/lib/services/video_thumbnail_service.dart +++ b/mobile/lib/services/video_thumbnail_service.dart @@ -431,7 +431,7 @@ class VideoThumbnailService { ); if (bytes != null && bytes.isNotEmpty) { - return _writeGhostFrame(bytes); + return await _writeGhostFrame(bytes); } Log.warning( @@ -475,7 +475,7 @@ class VideoThumbnailService { ); if (result != null) { - return _writeGhostFrame(result.bytes); + return await _writeGhostFrame(result.bytes); } Log.warning( diff --git a/mobile/lib/services/view_event_publisher.dart b/mobile/lib/services/view_event_publisher.dart index 3ee63500b8..c80643899e 100644 --- a/mobile/lib/services/view_event_publisher.dart +++ b/mobile/lib/services/view_event_publisher.dart @@ -30,12 +30,11 @@ enum ViewEventPhase { /// /// Wired in the app layer to Crashlytics for structural drops. Injected /// rather than reached for statically so tests can assert on drops. -typedef ViewEventDropReporter = - void Function( - ViewEventDropReason reason, { - required String videoId, - required String method, - }); +typedef ViewEventDropReporter = void Function( + ViewEventDropReason reason, { + required String videoId, + required String method, +}); /// Service for publishing video view events to Nostr relays. /// diff --git a/mobile/lib/services/web_iframe_nostr_bridge_binding.dart b/mobile/lib/services/web_iframe_nostr_bridge_binding.dart index 4b16b32480..25fcd3cd2e 100644 --- a/mobile/lib/services/web_iframe_nostr_bridge_binding.dart +++ b/mobile/lib/services/web_iframe_nostr_bridge_binding.dart @@ -1,9 +1,8 @@ -typedef WebIframeBridgeMessageHandler = - Future Function({ - required String origin, - required dynamic data, - required void Function(dynamic message, String targetOrigin) postReply, - }); +typedef WebIframeBridgeMessageHandler = Future Function({ + required String origin, + required dynamic data, + required void Function(dynamic message, String targetOrigin) postReply, +}); abstract class WebIframeNostrBridgeBinding { void start({ diff --git a/mobile/lib/services/zendesk_support_service.dart b/mobile/lib/services/zendesk_support_service.dart index fa2523cfc1..9bc5f767c7 100644 --- a/mobile/lib/services/zendesk_support_service.dart +++ b/mobile/lib/services/zendesk_support_service.dart @@ -14,11 +14,10 @@ import 'package:openvine/observability/crash_reporter.dart'; import 'package:openvine/services/nip98_auth_service.dart'; import 'package:unified_logger/unified_logger.dart'; -typedef JwtIdentityRefresh = - Future Function({ - required Nip98AuthService nip98Service, - required String relayManagerUrl, - }); +typedef JwtIdentityRefresh = Future Function({ + required Nip98AuthService nip98Service, + required String relayManagerUrl, +}); class ZendeskAttachmentUploadException implements Exception { const ZendeskAttachmentUploadException(); diff --git a/mobile/lib/startup/app_bootstrap.dart b/mobile/lib/startup/app_bootstrap.dart index 3888603a21..423da4f343 100644 --- a/mobile/lib/startup/app_bootstrap.dart +++ b/mobile/lib/startup/app_bootstrap.dart @@ -84,13 +84,14 @@ Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { await handleFirebaseMessagingBackgroundMessage(message); } -typedef ShorebirdTrackUpdate = - Future Function({ - required ShorebirdUpdater updater, - required SharedPreferences preferences, - }); -typedef UnexpectedShorebirdErrorReporter = - Future Function(Object error, StackTrace stackTrace); +typedef ShorebirdTrackUpdate = Future Function({ + required ShorebirdUpdater updater, + required SharedPreferences preferences, +}); +typedef UnexpectedShorebirdErrorReporter = Future Function( + Object error, + StackTrace stackTrace, +); @visibleForTesting Future configureVideoPlayerCacheForStartup({ @@ -864,8 +865,11 @@ String _startupPlatformName() { } /// Sink [handleUncaughtZoneError] files a report to. -typedef ZoneErrorRecorder = - Future Function(Object error, StackTrace stack, {String? reason}); +typedef ZoneErrorRecorder = Future Function( + Object error, + StackTrace stack, { + String? reason, +}); /// Logs, then files a report for, an error that escaped every `try`/`catch` /// in the app zone. diff --git a/mobile/lib/state/copy_with_sentinel.dart b/mobile/lib/state/copy_with_sentinel.dart new file mode 100644 index 0000000000..f776147ae9 --- /dev/null +++ b/mobile/lib/state/copy_with_sentinel.dart @@ -0,0 +1,13 @@ +// ABOUTME: Sentinel that lets a hand-written copyWith tell "argument omitted" +// ABOUTME: apart from "argument explicitly set to null" on nullable fields. + +/// Default for a nullable `copyWith` parameter. +/// +/// A plain `value ?? this.value` cannot clear a nullable field: passing +/// `null` is indistinguishable from omitting the argument, so the old value +/// survives and the call silently does nothing. These state classes were +/// generated by freezed before the Dart 3.13 migration, and freezed's +/// `copyWith` cleared the field — call sites such as `copyWith(error: null)` +/// rely on that. Comparing against this sentinel with [identical] keeps that +/// behaviour. +const Object unsetCopyWithArgument = Object(); diff --git a/mobile/lib/state/curation_state.dart b/mobile/lib/state/curation_state.dart index 46e1a25d5e..2b6c6fbd49 100644 --- a/mobile/lib/state/curation_state.dart +++ b/mobile/lib/state/curation_state.dart @@ -1,35 +1,53 @@ -// ABOUTME: Freezed state model for curation provider containing curated video sets +// ABOUTME: State model for curation provider containing curated video sets // ABOUTME: Manages only editor picks - trending/popular handled by infinite feeds -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:equatable/equatable.dart'; import 'package:models/models.dart'; - -part 'curation_state.freezed.dart'; +import 'package:openvine/state/copy_with_sentinel.dart'; +import 'package:openvine/state/equal_unmodifiable_collections.dart'; /// State model for curation provider (only Editor's Picks) -@freezed -sealed class CurationState with _$CurationState { - const factory CurationState({ - /// Editor's picks videos (classic vines) +class CurationState extends Equatable { + const CurationState({ required List editorsPicks, + required this.isLoading, + List trending = const [], + List curationSets = const [], + this.lastRefreshed, + this.error, + }) : _editorsPicks = editorsPicks, + _trending = trending, + _curationSets = curationSets; - /// Whether curation data is loading - required bool isLoading, + /// Editor's picks videos (classic vines) + final List _editorsPicks; + List get editorsPicks { + if (_editorsPicks is EqualUnmodifiableListView) return _editorsPicks; + return EqualUnmodifiableListView(_editorsPicks); + } - /// Trending videos (popular now) - @Default([]) List trending, + /// Whether curation data is loading + final bool isLoading; - /// All available curation sets - @Default([]) List curationSets, + /// Trending videos (popular now) + final List _trending; + List get trending { + if (_trending is EqualUnmodifiableListView) return _trending; + return EqualUnmodifiableListView(_trending); + } - /// Last refresh timestamp - DateTime? lastRefreshed, + /// All available curation sets + final List _curationSets; + List get curationSets { + if (_curationSets is EqualUnmodifiableListView) return _curationSets; + return EqualUnmodifiableListView(_curationSets); + } - /// Error message if any - String? error, - }) = _CurationState; + /// Last refresh timestamp + final DateTime? lastRefreshed; - const CurationState._(); + /// Error message if any + final String? error; /// Get total number of curated videos int get totalCuratedVideos => editorsPicks.length + trending.length; @@ -42,4 +60,36 @@ sealed class CurationState with _$CurationState { CurationSetType.editorsPicks => editorsPicks, CurationSetType.trending => trending, }; + + CurationState copyWith({ + List? editorsPicks, + bool? isLoading, + List? trending, + List? curationSets, + Object? lastRefreshed = unsetCopyWithArgument, + Object? error = unsetCopyWithArgument, + }) { + return CurationState( + editorsPicks: editorsPicks ?? _editorsPicks, + isLoading: isLoading ?? this.isLoading, + trending: trending ?? _trending, + curationSets: curationSets ?? _curationSets, + lastRefreshed: identical(lastRefreshed, unsetCopyWithArgument) + ? this.lastRefreshed + : lastRefreshed as DateTime?, + error: identical(error, unsetCopyWithArgument) + ? this.error + : error as String?, + ); + } + + @override + List get props => [ + _editorsPicks, + isLoading, + _trending, + _curationSets, + lastRefreshed, + error, + ]; } diff --git a/mobile/lib/state/curation_state.freezed.dart b/mobile/lib/state/curation_state.freezed.dart deleted file mode 100644 index f2b7a9e8e3..0000000000 --- a/mobile/lib/state/curation_state.freezed.dart +++ /dev/null @@ -1,313 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'curation_state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$CurationState { - -/// Editor's picks videos (classic vines) - List get editorsPicks;/// Whether curation data is loading - bool get isLoading;/// Trending videos (popular now) - List get trending;/// All available curation sets - List get curationSets;/// Last refresh timestamp - DateTime? get lastRefreshed;/// Error message if any - String? get error; -/// Create a copy of CurationState -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$CurationStateCopyWith get copyWith => _$CurationStateCopyWithImpl(this as CurationState, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is CurationState&&const DeepCollectionEquality().equals(other.editorsPicks, editorsPicks)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&const DeepCollectionEquality().equals(other.trending, trending)&&const DeepCollectionEquality().equals(other.curationSets, curationSets)&&(identical(other.lastRefreshed, lastRefreshed) || other.lastRefreshed == lastRefreshed)&&(identical(other.error, error) || other.error == error)); -} - - -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(editorsPicks),isLoading,const DeepCollectionEquality().hash(trending),const DeepCollectionEquality().hash(curationSets),lastRefreshed,error); - -@override -String toString() { - return 'CurationState(editorsPicks: $editorsPicks, isLoading: $isLoading, trending: $trending, curationSets: $curationSets, lastRefreshed: $lastRefreshed, error: $error)'; -} - - -} - -/// @nodoc -abstract mixin class $CurationStateCopyWith<$Res> { - factory $CurationStateCopyWith(CurationState value, $Res Function(CurationState) _then) = _$CurationStateCopyWithImpl; -@useResult -$Res call({ - List editorsPicks, bool isLoading, List trending, List curationSets, DateTime? lastRefreshed, String? error -}); - - - - -} -/// @nodoc -class _$CurationStateCopyWithImpl<$Res> - implements $CurationStateCopyWith<$Res> { - _$CurationStateCopyWithImpl(this._self, this._then); - - final CurationState _self; - final $Res Function(CurationState) _then; - -/// Create a copy of CurationState -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? editorsPicks = null,Object? isLoading = null,Object? trending = null,Object? curationSets = null,Object? lastRefreshed = freezed,Object? error = freezed,}) { - return _then(_self.copyWith( -editorsPicks: null == editorsPicks ? _self.editorsPicks : editorsPicks // ignore: cast_nullable_to_non_nullable -as List,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable -as bool,trending: null == trending ? _self.trending : trending // ignore: cast_nullable_to_non_nullable -as List,curationSets: null == curationSets ? _self.curationSets : curationSets // ignore: cast_nullable_to_non_nullable -as List,lastRefreshed: freezed == lastRefreshed ? _self.lastRefreshed : lastRefreshed // ignore: cast_nullable_to_non_nullable -as DateTime?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [CurationState]. -extension CurationStatePatterns on CurationState { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _CurationState value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _CurationState() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _CurationState value) $default,){ -final _that = this; -switch (_that) { -case _CurationState(): -return $default(_that);} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CurationState value)? $default,){ -final _that = this; -switch (_that) { -case _CurationState() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( List editorsPicks, bool isLoading, List trending, List curationSets, DateTime? lastRefreshed, String? error)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _CurationState() when $default != null: -return $default(_that.editorsPicks,_that.isLoading,_that.trending,_that.curationSets,_that.lastRefreshed,_that.error);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( List editorsPicks, bool isLoading, List trending, List curationSets, DateTime? lastRefreshed, String? error) $default,) {final _that = this; -switch (_that) { -case _CurationState(): -return $default(_that.editorsPicks,_that.isLoading,_that.trending,_that.curationSets,_that.lastRefreshed,_that.error);} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( List editorsPicks, bool isLoading, List trending, List curationSets, DateTime? lastRefreshed, String? error)? $default,) {final _that = this; -switch (_that) { -case _CurationState() when $default != null: -return $default(_that.editorsPicks,_that.isLoading,_that.trending,_that.curationSets,_that.lastRefreshed,_that.error);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _CurationState extends CurationState { - const _CurationState({required final List editorsPicks, required this.isLoading, final List trending = const [], final List curationSets = const [], this.lastRefreshed, this.error}): _editorsPicks = editorsPicks,_trending = trending,_curationSets = curationSets,super._(); - - -/// Editor's picks videos (classic vines) - final List _editorsPicks; -/// Editor's picks videos (classic vines) -@override List get editorsPicks { - if (_editorsPicks is EqualUnmodifiableListView) return _editorsPicks; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_editorsPicks); -} - -/// Whether curation data is loading -@override final bool isLoading; -/// Trending videos (popular now) - final List _trending; -/// Trending videos (popular now) -@override@JsonKey() List get trending { - if (_trending is EqualUnmodifiableListView) return _trending; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_trending); -} - -/// All available curation sets - final List _curationSets; -/// All available curation sets -@override@JsonKey() List get curationSets { - if (_curationSets is EqualUnmodifiableListView) return _curationSets; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_curationSets); -} - -/// Last refresh timestamp -@override final DateTime? lastRefreshed; -/// Error message if any -@override final String? error; - -/// Create a copy of CurationState -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$CurationStateCopyWith<_CurationState> get copyWith => __$CurationStateCopyWithImpl<_CurationState>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _CurationState&&const DeepCollectionEquality().equals(other._editorsPicks, _editorsPicks)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&const DeepCollectionEquality().equals(other._trending, _trending)&&const DeepCollectionEquality().equals(other._curationSets, _curationSets)&&(identical(other.lastRefreshed, lastRefreshed) || other.lastRefreshed == lastRefreshed)&&(identical(other.error, error) || other.error == error)); -} - - -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_editorsPicks),isLoading,const DeepCollectionEquality().hash(_trending),const DeepCollectionEquality().hash(_curationSets),lastRefreshed,error); - -@override -String toString() { - return 'CurationState(editorsPicks: $editorsPicks, isLoading: $isLoading, trending: $trending, curationSets: $curationSets, lastRefreshed: $lastRefreshed, error: $error)'; -} - - -} - -/// @nodoc -abstract mixin class _$CurationStateCopyWith<$Res> implements $CurationStateCopyWith<$Res> { - factory _$CurationStateCopyWith(_CurationState value, $Res Function(_CurationState) _then) = __$CurationStateCopyWithImpl; -@override @useResult -$Res call({ - List editorsPicks, bool isLoading, List trending, List curationSets, DateTime? lastRefreshed, String? error -}); - - - - -} -/// @nodoc -class __$CurationStateCopyWithImpl<$Res> - implements _$CurationStateCopyWith<$Res> { - __$CurationStateCopyWithImpl(this._self, this._then); - - final _CurationState _self; - final $Res Function(_CurationState) _then; - -/// Create a copy of CurationState -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? editorsPicks = null,Object? isLoading = null,Object? trending = null,Object? curationSets = null,Object? lastRefreshed = freezed,Object? error = freezed,}) { - return _then(_CurationState( -editorsPicks: null == editorsPicks ? _self._editorsPicks : editorsPicks // ignore: cast_nullable_to_non_nullable -as List,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable -as bool,trending: null == trending ? _self._trending : trending // ignore: cast_nullable_to_non_nullable -as List,curationSets: null == curationSets ? _self._curationSets : curationSets // ignore: cast_nullable_to_non_nullable -as List,lastRefreshed: freezed == lastRefreshed ? _self.lastRefreshed : lastRefreshed // ignore: cast_nullable_to_non_nullable -as DateTime?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - - -} - -// dart format on diff --git a/mobile/lib/state/equal_unmodifiable_collections.dart b/mobile/lib/state/equal_unmodifiable_collections.dart new file mode 100644 index 0000000000..f569072216 --- /dev/null +++ b/mobile/lib/state/equal_unmodifiable_collections.dart @@ -0,0 +1,77 @@ +// ABOUTME: Unmodifiable collection views that compare equal when they wrap the +// ABOUTME: same source, so `select` on a state collection can still memoise. + +import 'dart:collection'; + +import 'package:meta/meta.dart'; + +/// An [UnmodifiableListView] that compares equal to another view over the same +/// source list. +/// +/// A state getter has to hand out a fresh view on every read — it cannot cache +/// one in a field without giving up its `const` constructor. `dart:collection` +/// views inherit identity `==`, so two reads of the same unchanged field never +/// compare equal, and `ref.watch(fooProvider.select((s) => s.items))` re-fires +/// on every unrelated state change. Freezed shipped these three wrappers for +/// exactly that reason; the handwritten state models keep them. +/// +/// Equality is deliberately identity of the *source*, matching freezed: the +/// question a selector asks is "did this field get replaced", and the source +/// list survives a `copyWith` that does not touch it. +@immutable +class EqualUnmodifiableListView extends UnmodifiableListView { + /// Wraps [_source] without copying it. + EqualUnmodifiableListView(this._source) : super(_source); + + final Iterable _source; + + @override + bool operator ==(Object other) { + return other is EqualUnmodifiableListView && + other.runtimeType == runtimeType && + other._source == _source; + } + + @override + int get hashCode => Object.hash(runtimeType, _source); +} + +/// An [UnmodifiableSetView] that compares equal to another view over the same +/// source set. See [EqualUnmodifiableListView]. +@immutable +class EqualUnmodifiableSetView extends UnmodifiableSetView { + /// Wraps [_source] without copying it. + EqualUnmodifiableSetView(this._source) : super(_source); + + final Set _source; + + @override + bool operator ==(Object other) { + return other is EqualUnmodifiableSetView && + other.runtimeType == runtimeType && + other._source == _source; + } + + @override + int get hashCode => Object.hash(runtimeType, _source); +} + +/// An [UnmodifiableMapView] that compares equal to another view over the same +/// source map. See [EqualUnmodifiableListView]. +@immutable +class EqualUnmodifiableMapView extends UnmodifiableMapView { + /// Wraps [_source] without copying it. + EqualUnmodifiableMapView(this._source) : super(_source); + + final Map _source; + + @override + bool operator ==(Object other) { + return other is EqualUnmodifiableMapView && + other.runtimeType == runtimeType && + other._source == _source; + } + + @override + int get hashCode => Object.hash(runtimeType, _source); +} diff --git a/mobile/lib/state/seen_videos_state.dart b/mobile/lib/state/seen_videos_state.dart index 82f29456f7..145193a688 100644 --- a/mobile/lib/state/seen_videos_state.dart +++ b/mobile/lib/state/seen_videos_state.dart @@ -1,17 +1,33 @@ // ABOUTME: State class for tracking seen videos with immutable state pattern // ABOUTME: Used by SeenVideosNotifier for reactive state management -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:equatable/equatable.dart'; +import 'package:openvine/state/equal_unmodifiable_collections.dart'; -part 'seen_videos_state.freezed.dart'; - -@freezed -sealed class SeenVideosState with _$SeenVideosState { - const factory SeenVideosState({ - @Default({}) Set seenVideoIds, - @Default(false) bool isInitialized, - }) = _SeenVideosState; +class SeenVideosState extends Equatable { + const SeenVideosState({ + Set seenVideoIds = const {}, + this.isInitialized = false, + }) : _seenVideoIds = seenVideoIds; /// Initial empty state static const initial = SeenVideosState(); + + final Set _seenVideoIds; + Set get seenVideoIds { + if (_seenVideoIds is EqualUnmodifiableSetView) return _seenVideoIds; + return EqualUnmodifiableSetView(_seenVideoIds); + } + + final bool isInitialized; + + SeenVideosState copyWith({Set? seenVideoIds, bool? isInitialized}) { + return SeenVideosState( + seenVideoIds: seenVideoIds ?? _seenVideoIds, + isInitialized: isInitialized ?? this.isInitialized, + ); + } + + @override + List get props => [_seenVideoIds, isInitialized]; } diff --git a/mobile/lib/state/seen_videos_state.freezed.dart b/mobile/lib/state/seen_videos_state.freezed.dart deleted file mode 100644 index 86864436b0..0000000000 --- a/mobile/lib/state/seen_videos_state.freezed.dart +++ /dev/null @@ -1,274 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'seen_videos_state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$SeenVideosState { - - Set get seenVideoIds; bool get isInitialized; -/// Create a copy of SeenVideosState -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$SeenVideosStateCopyWith get copyWith => _$SeenVideosStateCopyWithImpl(this as SeenVideosState, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is SeenVideosState&&const DeepCollectionEquality().equals(other.seenVideoIds, seenVideoIds)&&(identical(other.isInitialized, isInitialized) || other.isInitialized == isInitialized)); -} - - -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(seenVideoIds),isInitialized); - -@override -String toString() { - return 'SeenVideosState(seenVideoIds: $seenVideoIds, isInitialized: $isInitialized)'; -} - - -} - -/// @nodoc -abstract mixin class $SeenVideosStateCopyWith<$Res> { - factory $SeenVideosStateCopyWith(SeenVideosState value, $Res Function(SeenVideosState) _then) = _$SeenVideosStateCopyWithImpl; -@useResult -$Res call({ - Set seenVideoIds, bool isInitialized -}); - - - - -} -/// @nodoc -class _$SeenVideosStateCopyWithImpl<$Res> - implements $SeenVideosStateCopyWith<$Res> { - _$SeenVideosStateCopyWithImpl(this._self, this._then); - - final SeenVideosState _self; - final $Res Function(SeenVideosState) _then; - -/// Create a copy of SeenVideosState -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? seenVideoIds = null,Object? isInitialized = null,}) { - return _then(_self.copyWith( -seenVideoIds: null == seenVideoIds ? _self.seenVideoIds : seenVideoIds // ignore: cast_nullable_to_non_nullable -as Set,isInitialized: null == isInitialized ? _self.isInitialized : isInitialized // ignore: cast_nullable_to_non_nullable -as bool, - )); -} - -} - - -/// Adds pattern-matching-related methods to [SeenVideosState]. -extension SeenVideosStatePatterns on SeenVideosState { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _SeenVideosState value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _SeenVideosState() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _SeenVideosState value) $default,){ -final _that = this; -switch (_that) { -case _SeenVideosState(): -return $default(_that);} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SeenVideosState value)? $default,){ -final _that = this; -switch (_that) { -case _SeenVideosState() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( Set seenVideoIds, bool isInitialized)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _SeenVideosState() when $default != null: -return $default(_that.seenVideoIds,_that.isInitialized);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( Set seenVideoIds, bool isInitialized) $default,) {final _that = this; -switch (_that) { -case _SeenVideosState(): -return $default(_that.seenVideoIds,_that.isInitialized);} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( Set seenVideoIds, bool isInitialized)? $default,) {final _that = this; -switch (_that) { -case _SeenVideosState() when $default != null: -return $default(_that.seenVideoIds,_that.isInitialized);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _SeenVideosState implements SeenVideosState { - const _SeenVideosState({final Set seenVideoIds = const {}, this.isInitialized = false}): _seenVideoIds = seenVideoIds; - - - final Set _seenVideoIds; -@override@JsonKey() Set get seenVideoIds { - if (_seenVideoIds is EqualUnmodifiableSetView) return _seenVideoIds; - // ignore: implicit_dynamic_type - return EqualUnmodifiableSetView(_seenVideoIds); -} - -@override@JsonKey() final bool isInitialized; - -/// Create a copy of SeenVideosState -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$SeenVideosStateCopyWith<_SeenVideosState> get copyWith => __$SeenVideosStateCopyWithImpl<_SeenVideosState>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _SeenVideosState&&const DeepCollectionEquality().equals(other._seenVideoIds, _seenVideoIds)&&(identical(other.isInitialized, isInitialized) || other.isInitialized == isInitialized)); -} - - -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_seenVideoIds),isInitialized); - -@override -String toString() { - return 'SeenVideosState(seenVideoIds: $seenVideoIds, isInitialized: $isInitialized)'; -} - - -} - -/// @nodoc -abstract mixin class _$SeenVideosStateCopyWith<$Res> implements $SeenVideosStateCopyWith<$Res> { - factory _$SeenVideosStateCopyWith(_SeenVideosState value, $Res Function(_SeenVideosState) _then) = __$SeenVideosStateCopyWithImpl; -@override @useResult -$Res call({ - Set seenVideoIds, bool isInitialized -}); - - - - -} -/// @nodoc -class __$SeenVideosStateCopyWithImpl<$Res> - implements _$SeenVideosStateCopyWith<$Res> { - __$SeenVideosStateCopyWithImpl(this._self, this._then); - - final _SeenVideosState _self; - final $Res Function(_SeenVideosState) _then; - -/// Create a copy of SeenVideosState -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? seenVideoIds = null,Object? isInitialized = null,}) { - return _then(_SeenVideosState( -seenVideoIds: null == seenVideoIds ? _self._seenVideoIds : seenVideoIds // ignore: cast_nullable_to_non_nullable -as Set,isInitialized: null == isInitialized ? _self.isInitialized : isInitialized // ignore: cast_nullable_to_non_nullable -as bool, - )); -} - - -} - -// dart format on diff --git a/mobile/lib/state/user_profile_state.dart b/mobile/lib/state/user_profile_state.dart index 7f4ec95085..523a6ed595 100644 --- a/mobile/lib/state/user_profile_state.dart +++ b/mobile/lib/state/user_profile_state.dart @@ -1,40 +1,75 @@ // ABOUTME: User profile state model for managing profile cache and loading states // ABOUTME: Used by Riverpod UserProfileProvider to manage reactive profile state -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:openvine/state/copy_with_sentinel.dart'; +import 'package:openvine/state/equal_unmodifiable_collections.dart'; -part 'user_profile_state.freezed.dart'; part 'user_profile_state.g.dart'; -@freezed -sealed class UserProfileState with _$UserProfileState { - const factory UserProfileState({ - // Pending profile requests - @Default({}) Set pendingRequests, +@JsonSerializable() +class UserProfileState extends Equatable { + const UserProfileState({ + Set pendingRequests = const {}, + Set knownMissingProfiles = const {}, + Map missingProfileRetryAfter = const {}, + Set pendingBatchPubkeys = const {}, + this.isLoading = false, + this.isInitialized = false, + this.error, + this.totalProfilesRequested = 0, + }) : _pendingRequests = pendingRequests, + _knownMissingProfiles = knownMissingProfiles, + _missingProfileRetryAfter = missingProfileRetryAfter, + _pendingBatchPubkeys = pendingBatchPubkeys; - // Missing profiles to avoid spam - @Default({}) Set knownMissingProfiles, - @Default({}) Map missingProfileRetryAfter, + factory UserProfileState.fromJson(Map json) => + _$UserProfileStateFromJson(json); - // Batch fetching state - @Default({}) Set pendingBatchPubkeys, + /// Create initial state + static const UserProfileState initial = UserProfileState(); - // Loading and error state - @Default(false) bool isLoading, - @Default(false) bool isInitialized, - String? error, + // Pending profile requests + final Set _pendingRequests; + Set get pendingRequests { + if (_pendingRequests is EqualUnmodifiableSetView) return _pendingRequests; + return EqualUnmodifiableSetView(_pendingRequests); + } - // Stats - @Default(0) int totalProfilesRequested, - }) = _UserProfileState; + // Missing profiles to avoid spam + final Set _knownMissingProfiles; + Set get knownMissingProfiles { + if (_knownMissingProfiles is EqualUnmodifiableSetView) { + return _knownMissingProfiles; + } + return EqualUnmodifiableSetView(_knownMissingProfiles); + } - factory UserProfileState.fromJson(Map json) => - _$UserProfileStateFromJson(json); + final Map _missingProfileRetryAfter; + Map get missingProfileRetryAfter { + if (_missingProfileRetryAfter is EqualUnmodifiableMapView) { + return _missingProfileRetryAfter; + } + return EqualUnmodifiableMapView(_missingProfileRetryAfter); + } - const UserProfileState._(); + // Batch fetching state + final Set _pendingBatchPubkeys; + Set get pendingBatchPubkeys { + if (_pendingBatchPubkeys is EqualUnmodifiableSetView) { + return _pendingBatchPubkeys; + } + return EqualUnmodifiableSetView(_pendingBatchPubkeys); + } - /// Create initial state - static const UserProfileState initial = UserProfileState(); + // Loading and error state + final bool isLoading; + final bool isInitialized; + final String? error; + + // Stats + final int totalProfilesRequested; /// Check if profile request is pending bool isRequestPending(String pubkey) => pendingRequests.contains(pubkey); @@ -48,4 +83,44 @@ sealed class UserProfileState with _$UserProfileState { return DateTime.now().isBefore(retryAfter); } + + Map toJson() => _$UserProfileStateToJson(this); + + UserProfileState copyWith({ + Set? pendingRequests, + Set? knownMissingProfiles, + Map? missingProfileRetryAfter, + Set? pendingBatchPubkeys, + bool? isLoading, + bool? isInitialized, + Object? error = unsetCopyWithArgument, + int? totalProfilesRequested, + }) { + return UserProfileState( + pendingRequests: pendingRequests ?? _pendingRequests, + knownMissingProfiles: knownMissingProfiles ?? _knownMissingProfiles, + missingProfileRetryAfter: + missingProfileRetryAfter ?? _missingProfileRetryAfter, + pendingBatchPubkeys: pendingBatchPubkeys ?? _pendingBatchPubkeys, + isLoading: isLoading ?? this.isLoading, + isInitialized: isInitialized ?? this.isInitialized, + error: identical(error, unsetCopyWithArgument) + ? this.error + : error as String?, + totalProfilesRequested: + totalProfilesRequested ?? this.totalProfilesRequested, + ); + } + + @override + List get props => [ + _pendingRequests, + _knownMissingProfiles, + _missingProfileRetryAfter, + _pendingBatchPubkeys, + isLoading, + isInitialized, + error, + totalProfilesRequested, + ]; } diff --git a/mobile/lib/state/user_profile_state.freezed.dart b/mobile/lib/state/user_profile_state.freezed.dart deleted file mode 100644 index 7e2aea9d0a..0000000000 --- a/mobile/lib/state/user_profile_state.freezed.dart +++ /dev/null @@ -1,316 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'user_profile_state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; - -/// @nodoc -mixin _$UserProfileState { - - Set get pendingRequests; Set get knownMissingProfiles; Map get missingProfileRetryAfter; Set get pendingBatchPubkeys; bool get isLoading; bool get isInitialized; String? get error; int get totalProfilesRequested; -/// Create a copy of UserProfileState -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$UserProfileStateCopyWith get copyWith => _$UserProfileStateCopyWithImpl(this as UserProfileState, _$identity); - - /// Serializes this UserProfileState to a JSON map. - Map toJson(); - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is UserProfileState&&const DeepCollectionEquality().equals(other.pendingRequests, pendingRequests)&&const DeepCollectionEquality().equals(other.knownMissingProfiles, knownMissingProfiles)&&const DeepCollectionEquality().equals(other.missingProfileRetryAfter, missingProfileRetryAfter)&&const DeepCollectionEquality().equals(other.pendingBatchPubkeys, pendingBatchPubkeys)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isInitialized, isInitialized) || other.isInitialized == isInitialized)&&(identical(other.error, error) || other.error == error)&&(identical(other.totalProfilesRequested, totalProfilesRequested) || other.totalProfilesRequested == totalProfilesRequested)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(pendingRequests),const DeepCollectionEquality().hash(knownMissingProfiles),const DeepCollectionEquality().hash(missingProfileRetryAfter),const DeepCollectionEquality().hash(pendingBatchPubkeys),isLoading,isInitialized,error,totalProfilesRequested); - -@override -String toString() { - return 'UserProfileState(pendingRequests: $pendingRequests, knownMissingProfiles: $knownMissingProfiles, missingProfileRetryAfter: $missingProfileRetryAfter, pendingBatchPubkeys: $pendingBatchPubkeys, isLoading: $isLoading, isInitialized: $isInitialized, error: $error, totalProfilesRequested: $totalProfilesRequested)'; -} - - -} - -/// @nodoc -abstract mixin class $UserProfileStateCopyWith<$Res> { - factory $UserProfileStateCopyWith(UserProfileState value, $Res Function(UserProfileState) _then) = _$UserProfileStateCopyWithImpl; -@useResult -$Res call({ - Set pendingRequests, Set knownMissingProfiles, Map missingProfileRetryAfter, Set pendingBatchPubkeys, bool isLoading, bool isInitialized, String? error, int totalProfilesRequested -}); - - - - -} -/// @nodoc -class _$UserProfileStateCopyWithImpl<$Res> - implements $UserProfileStateCopyWith<$Res> { - _$UserProfileStateCopyWithImpl(this._self, this._then); - - final UserProfileState _self; - final $Res Function(UserProfileState) _then; - -/// Create a copy of UserProfileState -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? pendingRequests = null,Object? knownMissingProfiles = null,Object? missingProfileRetryAfter = null,Object? pendingBatchPubkeys = null,Object? isLoading = null,Object? isInitialized = null,Object? error = freezed,Object? totalProfilesRequested = null,}) { - return _then(_self.copyWith( -pendingRequests: null == pendingRequests ? _self.pendingRequests : pendingRequests // ignore: cast_nullable_to_non_nullable -as Set,knownMissingProfiles: null == knownMissingProfiles ? _self.knownMissingProfiles : knownMissingProfiles // ignore: cast_nullable_to_non_nullable -as Set,missingProfileRetryAfter: null == missingProfileRetryAfter ? _self.missingProfileRetryAfter : missingProfileRetryAfter // ignore: cast_nullable_to_non_nullable -as Map,pendingBatchPubkeys: null == pendingBatchPubkeys ? _self.pendingBatchPubkeys : pendingBatchPubkeys // ignore: cast_nullable_to_non_nullable -as Set,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable -as bool,isInitialized: null == isInitialized ? _self.isInitialized : isInitialized // ignore: cast_nullable_to_non_nullable -as bool,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable -as String?,totalProfilesRequested: null == totalProfilesRequested ? _self.totalProfilesRequested : totalProfilesRequested // ignore: cast_nullable_to_non_nullable -as int, - )); -} - -} - - -/// Adds pattern-matching-related methods to [UserProfileState]. -extension UserProfileStatePatterns on UserProfileState { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _UserProfileState value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _UserProfileState() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _UserProfileState value) $default,){ -final _that = this; -switch (_that) { -case _UserProfileState(): -return $default(_that);} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _UserProfileState value)? $default,){ -final _that = this; -switch (_that) { -case _UserProfileState() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( Set pendingRequests, Set knownMissingProfiles, Map missingProfileRetryAfter, Set pendingBatchPubkeys, bool isLoading, bool isInitialized, String? error, int totalProfilesRequested)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _UserProfileState() when $default != null: -return $default(_that.pendingRequests,_that.knownMissingProfiles,_that.missingProfileRetryAfter,_that.pendingBatchPubkeys,_that.isLoading,_that.isInitialized,_that.error,_that.totalProfilesRequested);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( Set pendingRequests, Set knownMissingProfiles, Map missingProfileRetryAfter, Set pendingBatchPubkeys, bool isLoading, bool isInitialized, String? error, int totalProfilesRequested) $default,) {final _that = this; -switch (_that) { -case _UserProfileState(): -return $default(_that.pendingRequests,_that.knownMissingProfiles,_that.missingProfileRetryAfter,_that.pendingBatchPubkeys,_that.isLoading,_that.isInitialized,_that.error,_that.totalProfilesRequested);} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( Set pendingRequests, Set knownMissingProfiles, Map missingProfileRetryAfter, Set pendingBatchPubkeys, bool isLoading, bool isInitialized, String? error, int totalProfilesRequested)? $default,) {final _that = this; -switch (_that) { -case _UserProfileState() when $default != null: -return $default(_that.pendingRequests,_that.knownMissingProfiles,_that.missingProfileRetryAfter,_that.pendingBatchPubkeys,_that.isLoading,_that.isInitialized,_that.error,_that.totalProfilesRequested);case _: - return null; - -} -} - -} - -/// @nodoc -@JsonSerializable() - -class _UserProfileState extends UserProfileState { - const _UserProfileState({final Set pendingRequests = const {}, final Set knownMissingProfiles = const {}, final Map missingProfileRetryAfter = const {}, final Set pendingBatchPubkeys = const {}, this.isLoading = false, this.isInitialized = false, this.error, this.totalProfilesRequested = 0}): _pendingRequests = pendingRequests,_knownMissingProfiles = knownMissingProfiles,_missingProfileRetryAfter = missingProfileRetryAfter,_pendingBatchPubkeys = pendingBatchPubkeys,super._(); - factory _UserProfileState.fromJson(Map json) => _$UserProfileStateFromJson(json); - - final Set _pendingRequests; -@override@JsonKey() Set get pendingRequests { - if (_pendingRequests is EqualUnmodifiableSetView) return _pendingRequests; - // ignore: implicit_dynamic_type - return EqualUnmodifiableSetView(_pendingRequests); -} - - final Set _knownMissingProfiles; -@override@JsonKey() Set get knownMissingProfiles { - if (_knownMissingProfiles is EqualUnmodifiableSetView) return _knownMissingProfiles; - // ignore: implicit_dynamic_type - return EqualUnmodifiableSetView(_knownMissingProfiles); -} - - final Map _missingProfileRetryAfter; -@override@JsonKey() Map get missingProfileRetryAfter { - if (_missingProfileRetryAfter is EqualUnmodifiableMapView) return _missingProfileRetryAfter; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_missingProfileRetryAfter); -} - - final Set _pendingBatchPubkeys; -@override@JsonKey() Set get pendingBatchPubkeys { - if (_pendingBatchPubkeys is EqualUnmodifiableSetView) return _pendingBatchPubkeys; - // ignore: implicit_dynamic_type - return EqualUnmodifiableSetView(_pendingBatchPubkeys); -} - -@override@JsonKey() final bool isLoading; -@override@JsonKey() final bool isInitialized; -@override final String? error; -@override@JsonKey() final int totalProfilesRequested; - -/// Create a copy of UserProfileState -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$UserProfileStateCopyWith<_UserProfileState> get copyWith => __$UserProfileStateCopyWithImpl<_UserProfileState>(this, _$identity); - -@override -Map toJson() { - return _$UserProfileStateToJson(this, ); -} - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _UserProfileState&&const DeepCollectionEquality().equals(other._pendingRequests, _pendingRequests)&&const DeepCollectionEquality().equals(other._knownMissingProfiles, _knownMissingProfiles)&&const DeepCollectionEquality().equals(other._missingProfileRetryAfter, _missingProfileRetryAfter)&&const DeepCollectionEquality().equals(other._pendingBatchPubkeys, _pendingBatchPubkeys)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isInitialized, isInitialized) || other.isInitialized == isInitialized)&&(identical(other.error, error) || other.error == error)&&(identical(other.totalProfilesRequested, totalProfilesRequested) || other.totalProfilesRequested == totalProfilesRequested)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_pendingRequests),const DeepCollectionEquality().hash(_knownMissingProfiles),const DeepCollectionEquality().hash(_missingProfileRetryAfter),const DeepCollectionEquality().hash(_pendingBatchPubkeys),isLoading,isInitialized,error,totalProfilesRequested); - -@override -String toString() { - return 'UserProfileState(pendingRequests: $pendingRequests, knownMissingProfiles: $knownMissingProfiles, missingProfileRetryAfter: $missingProfileRetryAfter, pendingBatchPubkeys: $pendingBatchPubkeys, isLoading: $isLoading, isInitialized: $isInitialized, error: $error, totalProfilesRequested: $totalProfilesRequested)'; -} - - -} - -/// @nodoc -abstract mixin class _$UserProfileStateCopyWith<$Res> implements $UserProfileStateCopyWith<$Res> { - factory _$UserProfileStateCopyWith(_UserProfileState value, $Res Function(_UserProfileState) _then) = __$UserProfileStateCopyWithImpl; -@override @useResult -$Res call({ - Set pendingRequests, Set knownMissingProfiles, Map missingProfileRetryAfter, Set pendingBatchPubkeys, bool isLoading, bool isInitialized, String? error, int totalProfilesRequested -}); - - - - -} -/// @nodoc -class __$UserProfileStateCopyWithImpl<$Res> - implements _$UserProfileStateCopyWith<$Res> { - __$UserProfileStateCopyWithImpl(this._self, this._then); - - final _UserProfileState _self; - final $Res Function(_UserProfileState) _then; - -/// Create a copy of UserProfileState -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? pendingRequests = null,Object? knownMissingProfiles = null,Object? missingProfileRetryAfter = null,Object? pendingBatchPubkeys = null,Object? isLoading = null,Object? isInitialized = null,Object? error = freezed,Object? totalProfilesRequested = null,}) { - return _then(_UserProfileState( -pendingRequests: null == pendingRequests ? _self._pendingRequests : pendingRequests // ignore: cast_nullable_to_non_nullable -as Set,knownMissingProfiles: null == knownMissingProfiles ? _self._knownMissingProfiles : knownMissingProfiles // ignore: cast_nullable_to_non_nullable -as Set,missingProfileRetryAfter: null == missingProfileRetryAfter ? _self._missingProfileRetryAfter : missingProfileRetryAfter // ignore: cast_nullable_to_non_nullable -as Map,pendingBatchPubkeys: null == pendingBatchPubkeys ? _self._pendingBatchPubkeys : pendingBatchPubkeys // ignore: cast_nullable_to_non_nullable -as Set,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable -as bool,isInitialized: null == isInitialized ? _self.isInitialized : isInitialized // ignore: cast_nullable_to_non_nullable -as bool,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable -as String?,totalProfilesRequested: null == totalProfilesRequested ? _self.totalProfilesRequested : totalProfilesRequested // ignore: cast_nullable_to_non_nullable -as int, - )); -} - - -} - -// dart format on diff --git a/mobile/lib/state/user_profile_state.g.dart b/mobile/lib/state/user_profile_state.g.dart index dd1ec6df9e..3b561aa223 100644 --- a/mobile/lib/state/user_profile_state.g.dart +++ b/mobile/lib/state/user_profile_state.g.dart @@ -6,8 +6,8 @@ part of 'user_profile_state.dart'; // JsonSerializableGenerator // ************************************************************************** -_UserProfileState _$UserProfileStateFromJson(Map json) => - _UserProfileState( +UserProfileState _$UserProfileStateFromJson(Map json) => + UserProfileState( pendingRequests: (json['pendingRequests'] as List?) ?.map((e) => e as String) @@ -35,7 +35,7 @@ _UserProfileState _$UserProfileStateFromJson(Map json) => (json['totalProfilesRequested'] as num?)?.toInt() ?? 0, ); -Map _$UserProfileStateToJson(_UserProfileState instance) => +Map _$UserProfileStateToJson(UserProfileState instance) => { 'pendingRequests': instance.pendingRequests.toList(), 'knownMissingProfiles': instance.knownMissingProfiles.toList(), diff --git a/mobile/lib/state/video_feed_state.dart b/mobile/lib/state/video_feed_state.dart index 47ed328d48..ef4b978dc7 100644 --- a/mobile/lib/state/video_feed_state.dart +++ b/mobile/lib/state/video_feed_state.dart @@ -1,58 +1,135 @@ // ABOUTME: Simple state model for video lists without global feed modes // ABOUTME: Represents the current state of a video list with basic metadata -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:equatable/equatable.dart'; import 'package:models/models.dart'; - -part 'video_feed_state.freezed.dart'; +import 'package:openvine/state/copy_with_sentinel.dart'; +import 'package:openvine/state/equal_unmodifiable_collections.dart'; /// State model for video lists -@freezed -sealed class VideoFeedState with _$VideoFeedState { - const factory VideoFeedState({ - /// List of videos in the feed +class VideoFeedState extends Equatable { + const VideoFeedState({ required List videos, + required this.hasMoreContent, + this.isLoadingMore = false, + this.isRefreshing = false, + this.isInitialLoad = false, + this.error, + this.lastUpdated, + Map> videoListSources = const {}, + Set listOnlyVideoIds = const {}, + this.totalVideoCount, + this.isFetchingTotalCount = false, + }) : _videos = videos, + _videoListSources = videoListSources, + _listOnlyVideoIds = listOnlyVideoIds; + + /// List of videos in the feed + final List _videos; + List get videos { + if (_videos is EqualUnmodifiableListView) return _videos; + return EqualUnmodifiableListView(_videos); + } + + /// Whether more content can be loaded + final bool hasMoreContent; - /// Whether more content can be loaded - required bool hasMoreContent, + /// Loading state for pagination + final bool isLoadingMore; - /// Loading state for pagination - @Default(false) bool isLoadingMore, + /// Refreshing state for pull-to-refresh + final bool isRefreshing; - /// Refreshing state for pull-to-refresh - @Default(false) bool isRefreshing, + /// Whether this is the initial load (videos may still be arriving) + /// When true and videos is empty, show loading indicator instead of empty + /// state + final bool isInitialLoad; - /// Whether this is the initial load (videos may still be arriving) - /// When true and videos is empty, show loading indicator instead of empty state - @Default(false) bool isInitialLoad, + /// Error message if any + final String? error; - /// Error message if any - String? error, + /// Timestamp of last update + final DateTime? lastUpdated; - /// Timestamp of last update - DateTime? lastUpdated, + /// Maps video IDs to the set of curated list IDs they appear in + /// Used to show "From list: X" attribution chip on videos + final Map> _videoListSources; + Map> get videoListSources { + if (_videoListSources is EqualUnmodifiableMapView) { + return _videoListSources; + } + return EqualUnmodifiableMapView(_videoListSources); + } - /// Maps video IDs to the set of curated list IDs they appear in - /// Used to show "From list: X" attribution chip on videos - @Default({}) Map> videoListSources, + /// Set of video IDs that appear ONLY from subscribed lists (not from follows) + /// These videos should show the list attribution chip in the UI + final Set _listOnlyVideoIds; + Set get listOnlyVideoIds { + if (_listOnlyVideoIds is EqualUnmodifiableSetView) { + return _listOnlyVideoIds; + } + return EqualUnmodifiableSetView(_listOnlyVideoIds); + } - /// Set of video IDs that appear ONLY from subscribed lists (not from follows) - /// These videos should show the list attribution chip in the UI - @Default({}) Set listOnlyVideoIds, + /// Total video count from the server's X-Total-Count header. + /// When available, this is more accurate than `videos.length` which + /// only reflects the number of loaded videos. + final int? totalVideoCount; - /// Total video count from the server's X-Total-Count header. - /// When available, this is more accurate than `videos.length` which - /// only reflects the number of loaded videos. - int? totalVideoCount, + /// Whether a REST call that will resolve [totalVideoCount] is currently + /// in flight. Stays `true` from the moment the fetch starts until it + /// settles (success, empty, or failure). UI uses this to distinguish + /// "still loading, authoritative count may still arrive" from + /// "settled, no authoritative count available — fall back to + /// videos.length". + final bool isFetchingTotalCount; - /// Whether a REST call that will resolve [totalVideoCount] is currently - /// in flight. Stays `true` from the moment the fetch starts until it - /// settles (success, empty, or failure). UI uses this to distinguish - /// "still loading, authoritative count may still arrive" from - /// "settled, no authoritative count available — fall back to - /// videos.length". - @Default(false) bool isFetchingTotalCount, - }) = _VideoFeedState; + VideoFeedState copyWith({ + List? videos, + bool? hasMoreContent, + bool? isLoadingMore, + bool? isRefreshing, + bool? isInitialLoad, + Object? error = unsetCopyWithArgument, + Object? lastUpdated = unsetCopyWithArgument, + Map>? videoListSources, + Set? listOnlyVideoIds, + Object? totalVideoCount = unsetCopyWithArgument, + bool? isFetchingTotalCount, + }) { + return VideoFeedState( + videos: videos ?? _videos, + hasMoreContent: hasMoreContent ?? this.hasMoreContent, + isLoadingMore: isLoadingMore ?? this.isLoadingMore, + isRefreshing: isRefreshing ?? this.isRefreshing, + isInitialLoad: isInitialLoad ?? this.isInitialLoad, + error: identical(error, unsetCopyWithArgument) + ? this.error + : error as String?, + lastUpdated: identical(lastUpdated, unsetCopyWithArgument) + ? this.lastUpdated + : lastUpdated as DateTime?, + videoListSources: videoListSources ?? _videoListSources, + listOnlyVideoIds: listOnlyVideoIds ?? _listOnlyVideoIds, + totalVideoCount: identical(totalVideoCount, unsetCopyWithArgument) + ? this.totalVideoCount + : totalVideoCount as int?, + isFetchingTotalCount: isFetchingTotalCount ?? this.isFetchingTotalCount, + ); + } - const VideoFeedState._(); + @override + List get props => [ + _videos, + hasMoreContent, + isLoadingMore, + isRefreshing, + isInitialLoad, + error, + lastUpdated, + _videoListSources, + _listOnlyVideoIds, + totalVideoCount, + isFetchingTotalCount, + ]; } diff --git a/mobile/lib/state/video_feed_state.freezed.dart b/mobile/lib/state/video_feed_state.freezed.dart deleted file mode 100644 index 83a8b0f39b..0000000000 --- a/mobile/lib/state/video_feed_state.freezed.dart +++ /dev/null @@ -1,360 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'video_feed_state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$VideoFeedState { - -/// List of videos in the feed - List get videos;/// Whether more content can be loaded - bool get hasMoreContent;/// Loading state for pagination - bool get isLoadingMore;/// Refreshing state for pull-to-refresh - bool get isRefreshing;/// Whether this is the initial load (videos may still be arriving) -/// When true and videos is empty, show loading indicator instead of empty state - bool get isInitialLoad;/// Error message if any - String? get error;/// Timestamp of last update - DateTime? get lastUpdated;/// Maps video IDs to the set of curated list IDs they appear in -/// Used to show "From list: X" attribution chip on videos - Map> get videoListSources;/// Set of video IDs that appear ONLY from subscribed lists (not from follows) -/// These videos should show the list attribution chip in the UI - Set get listOnlyVideoIds;/// Total video count from the server's X-Total-Count header. -/// When available, this is more accurate than `videos.length` which -/// only reflects the number of loaded videos. - int? get totalVideoCount;/// Whether a REST call that will resolve [totalVideoCount] is currently -/// in flight. Stays `true` from the moment the fetch starts until it -/// settles (success, empty, or failure). UI uses this to distinguish -/// "still loading, authoritative count may still arrive" from -/// "settled, no authoritative count available — fall back to -/// videos.length". - bool get isFetchingTotalCount; -/// Create a copy of VideoFeedState -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$VideoFeedStateCopyWith get copyWith => _$VideoFeedStateCopyWithImpl(this as VideoFeedState, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is VideoFeedState&&const DeepCollectionEquality().equals(other.videos, videos)&&(identical(other.hasMoreContent, hasMoreContent) || other.hasMoreContent == hasMoreContent)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.isRefreshing, isRefreshing) || other.isRefreshing == isRefreshing)&&(identical(other.isInitialLoad, isInitialLoad) || other.isInitialLoad == isInitialLoad)&&(identical(other.error, error) || other.error == error)&&(identical(other.lastUpdated, lastUpdated) || other.lastUpdated == lastUpdated)&&const DeepCollectionEquality().equals(other.videoListSources, videoListSources)&&const DeepCollectionEquality().equals(other.listOnlyVideoIds, listOnlyVideoIds)&&(identical(other.totalVideoCount, totalVideoCount) || other.totalVideoCount == totalVideoCount)&&(identical(other.isFetchingTotalCount, isFetchingTotalCount) || other.isFetchingTotalCount == isFetchingTotalCount)); -} - - -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(videos),hasMoreContent,isLoadingMore,isRefreshing,isInitialLoad,error,lastUpdated,const DeepCollectionEquality().hash(videoListSources),const DeepCollectionEquality().hash(listOnlyVideoIds),totalVideoCount,isFetchingTotalCount); - -@override -String toString() { - return 'VideoFeedState(videos: $videos, hasMoreContent: $hasMoreContent, isLoadingMore: $isLoadingMore, isRefreshing: $isRefreshing, isInitialLoad: $isInitialLoad, error: $error, lastUpdated: $lastUpdated, videoListSources: $videoListSources, listOnlyVideoIds: $listOnlyVideoIds, totalVideoCount: $totalVideoCount, isFetchingTotalCount: $isFetchingTotalCount)'; -} - - -} - -/// @nodoc -abstract mixin class $VideoFeedStateCopyWith<$Res> { - factory $VideoFeedStateCopyWith(VideoFeedState value, $Res Function(VideoFeedState) _then) = _$VideoFeedStateCopyWithImpl; -@useResult -$Res call({ - List videos, bool hasMoreContent, bool isLoadingMore, bool isRefreshing, bool isInitialLoad, String? error, DateTime? lastUpdated, Map> videoListSources, Set listOnlyVideoIds, int? totalVideoCount, bool isFetchingTotalCount -}); - - - - -} -/// @nodoc -class _$VideoFeedStateCopyWithImpl<$Res> - implements $VideoFeedStateCopyWith<$Res> { - _$VideoFeedStateCopyWithImpl(this._self, this._then); - - final VideoFeedState _self; - final $Res Function(VideoFeedState) _then; - -/// Create a copy of VideoFeedState -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? videos = null,Object? hasMoreContent = null,Object? isLoadingMore = null,Object? isRefreshing = null,Object? isInitialLoad = null,Object? error = freezed,Object? lastUpdated = freezed,Object? videoListSources = null,Object? listOnlyVideoIds = null,Object? totalVideoCount = freezed,Object? isFetchingTotalCount = null,}) { - return _then(_self.copyWith( -videos: null == videos ? _self.videos : videos // ignore: cast_nullable_to_non_nullable -as List,hasMoreContent: null == hasMoreContent ? _self.hasMoreContent : hasMoreContent // ignore: cast_nullable_to_non_nullable -as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable -as bool,isRefreshing: null == isRefreshing ? _self.isRefreshing : isRefreshing // ignore: cast_nullable_to_non_nullable -as bool,isInitialLoad: null == isInitialLoad ? _self.isInitialLoad : isInitialLoad // ignore: cast_nullable_to_non_nullable -as bool,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable -as String?,lastUpdated: freezed == lastUpdated ? _self.lastUpdated : lastUpdated // ignore: cast_nullable_to_non_nullable -as DateTime?,videoListSources: null == videoListSources ? _self.videoListSources : videoListSources // ignore: cast_nullable_to_non_nullable -as Map>,listOnlyVideoIds: null == listOnlyVideoIds ? _self.listOnlyVideoIds : listOnlyVideoIds // ignore: cast_nullable_to_non_nullable -as Set,totalVideoCount: freezed == totalVideoCount ? _self.totalVideoCount : totalVideoCount // ignore: cast_nullable_to_non_nullable -as int?,isFetchingTotalCount: null == isFetchingTotalCount ? _self.isFetchingTotalCount : isFetchingTotalCount // ignore: cast_nullable_to_non_nullable -as bool, - )); -} - -} - - -/// Adds pattern-matching-related methods to [VideoFeedState]. -extension VideoFeedStatePatterns on VideoFeedState { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _VideoFeedState value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _VideoFeedState() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _VideoFeedState value) $default,){ -final _that = this; -switch (_that) { -case _VideoFeedState(): -return $default(_that);} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VideoFeedState value)? $default,){ -final _that = this; -switch (_that) { -case _VideoFeedState() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( List videos, bool hasMoreContent, bool isLoadingMore, bool isRefreshing, bool isInitialLoad, String? error, DateTime? lastUpdated, Map> videoListSources, Set listOnlyVideoIds, int? totalVideoCount, bool isFetchingTotalCount)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _VideoFeedState() when $default != null: -return $default(_that.videos,_that.hasMoreContent,_that.isLoadingMore,_that.isRefreshing,_that.isInitialLoad,_that.error,_that.lastUpdated,_that.videoListSources,_that.listOnlyVideoIds,_that.totalVideoCount,_that.isFetchingTotalCount);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( List videos, bool hasMoreContent, bool isLoadingMore, bool isRefreshing, bool isInitialLoad, String? error, DateTime? lastUpdated, Map> videoListSources, Set listOnlyVideoIds, int? totalVideoCount, bool isFetchingTotalCount) $default,) {final _that = this; -switch (_that) { -case _VideoFeedState(): -return $default(_that.videos,_that.hasMoreContent,_that.isLoadingMore,_that.isRefreshing,_that.isInitialLoad,_that.error,_that.lastUpdated,_that.videoListSources,_that.listOnlyVideoIds,_that.totalVideoCount,_that.isFetchingTotalCount);} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( List videos, bool hasMoreContent, bool isLoadingMore, bool isRefreshing, bool isInitialLoad, String? error, DateTime? lastUpdated, Map> videoListSources, Set listOnlyVideoIds, int? totalVideoCount, bool isFetchingTotalCount)? $default,) {final _that = this; -switch (_that) { -case _VideoFeedState() when $default != null: -return $default(_that.videos,_that.hasMoreContent,_that.isLoadingMore,_that.isRefreshing,_that.isInitialLoad,_that.error,_that.lastUpdated,_that.videoListSources,_that.listOnlyVideoIds,_that.totalVideoCount,_that.isFetchingTotalCount);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _VideoFeedState extends VideoFeedState { - const _VideoFeedState({required final List videos, required this.hasMoreContent, this.isLoadingMore = false, this.isRefreshing = false, this.isInitialLoad = false, this.error, this.lastUpdated, final Map> videoListSources = const {}, final Set listOnlyVideoIds = const {}, this.totalVideoCount, this.isFetchingTotalCount = false}): _videos = videos,_videoListSources = videoListSources,_listOnlyVideoIds = listOnlyVideoIds,super._(); - - -/// List of videos in the feed - final List _videos; -/// List of videos in the feed -@override List get videos { - if (_videos is EqualUnmodifiableListView) return _videos; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_videos); -} - -/// Whether more content can be loaded -@override final bool hasMoreContent; -/// Loading state for pagination -@override@JsonKey() final bool isLoadingMore; -/// Refreshing state for pull-to-refresh -@override@JsonKey() final bool isRefreshing; -/// Whether this is the initial load (videos may still be arriving) -/// When true and videos is empty, show loading indicator instead of empty state -@override@JsonKey() final bool isInitialLoad; -/// Error message if any -@override final String? error; -/// Timestamp of last update -@override final DateTime? lastUpdated; -/// Maps video IDs to the set of curated list IDs they appear in -/// Used to show "From list: X" attribution chip on videos - final Map> _videoListSources; -/// Maps video IDs to the set of curated list IDs they appear in -/// Used to show "From list: X" attribution chip on videos -@override@JsonKey() Map> get videoListSources { - if (_videoListSources is EqualUnmodifiableMapView) return _videoListSources; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_videoListSources); -} - -/// Set of video IDs that appear ONLY from subscribed lists (not from follows) -/// These videos should show the list attribution chip in the UI - final Set _listOnlyVideoIds; -/// Set of video IDs that appear ONLY from subscribed lists (not from follows) -/// These videos should show the list attribution chip in the UI -@override@JsonKey() Set get listOnlyVideoIds { - if (_listOnlyVideoIds is EqualUnmodifiableSetView) return _listOnlyVideoIds; - // ignore: implicit_dynamic_type - return EqualUnmodifiableSetView(_listOnlyVideoIds); -} - -/// Total video count from the server's X-Total-Count header. -/// When available, this is more accurate than `videos.length` which -/// only reflects the number of loaded videos. -@override final int? totalVideoCount; -/// Whether a REST call that will resolve [totalVideoCount] is currently -/// in flight. Stays `true` from the moment the fetch starts until it -/// settles (success, empty, or failure). UI uses this to distinguish -/// "still loading, authoritative count may still arrive" from -/// "settled, no authoritative count available — fall back to -/// videos.length". -@override@JsonKey() final bool isFetchingTotalCount; - -/// Create a copy of VideoFeedState -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$VideoFeedStateCopyWith<_VideoFeedState> get copyWith => __$VideoFeedStateCopyWithImpl<_VideoFeedState>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _VideoFeedState&&const DeepCollectionEquality().equals(other._videos, _videos)&&(identical(other.hasMoreContent, hasMoreContent) || other.hasMoreContent == hasMoreContent)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.isRefreshing, isRefreshing) || other.isRefreshing == isRefreshing)&&(identical(other.isInitialLoad, isInitialLoad) || other.isInitialLoad == isInitialLoad)&&(identical(other.error, error) || other.error == error)&&(identical(other.lastUpdated, lastUpdated) || other.lastUpdated == lastUpdated)&&const DeepCollectionEquality().equals(other._videoListSources, _videoListSources)&&const DeepCollectionEquality().equals(other._listOnlyVideoIds, _listOnlyVideoIds)&&(identical(other.totalVideoCount, totalVideoCount) || other.totalVideoCount == totalVideoCount)&&(identical(other.isFetchingTotalCount, isFetchingTotalCount) || other.isFetchingTotalCount == isFetchingTotalCount)); -} - - -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_videos),hasMoreContent,isLoadingMore,isRefreshing,isInitialLoad,error,lastUpdated,const DeepCollectionEquality().hash(_videoListSources),const DeepCollectionEquality().hash(_listOnlyVideoIds),totalVideoCount,isFetchingTotalCount); - -@override -String toString() { - return 'VideoFeedState(videos: $videos, hasMoreContent: $hasMoreContent, isLoadingMore: $isLoadingMore, isRefreshing: $isRefreshing, isInitialLoad: $isInitialLoad, error: $error, lastUpdated: $lastUpdated, videoListSources: $videoListSources, listOnlyVideoIds: $listOnlyVideoIds, totalVideoCount: $totalVideoCount, isFetchingTotalCount: $isFetchingTotalCount)'; -} - - -} - -/// @nodoc -abstract mixin class _$VideoFeedStateCopyWith<$Res> implements $VideoFeedStateCopyWith<$Res> { - factory _$VideoFeedStateCopyWith(_VideoFeedState value, $Res Function(_VideoFeedState) _then) = __$VideoFeedStateCopyWithImpl; -@override @useResult -$Res call({ - List videos, bool hasMoreContent, bool isLoadingMore, bool isRefreshing, bool isInitialLoad, String? error, DateTime? lastUpdated, Map> videoListSources, Set listOnlyVideoIds, int? totalVideoCount, bool isFetchingTotalCount -}); - - - - -} -/// @nodoc -class __$VideoFeedStateCopyWithImpl<$Res> - implements _$VideoFeedStateCopyWith<$Res> { - __$VideoFeedStateCopyWithImpl(this._self, this._then); - - final _VideoFeedState _self; - final $Res Function(_VideoFeedState) _then; - -/// Create a copy of VideoFeedState -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? videos = null,Object? hasMoreContent = null,Object? isLoadingMore = null,Object? isRefreshing = null,Object? isInitialLoad = null,Object? error = freezed,Object? lastUpdated = freezed,Object? videoListSources = null,Object? listOnlyVideoIds = null,Object? totalVideoCount = freezed,Object? isFetchingTotalCount = null,}) { - return _then(_VideoFeedState( -videos: null == videos ? _self._videos : videos // ignore: cast_nullable_to_non_nullable -as List,hasMoreContent: null == hasMoreContent ? _self.hasMoreContent : hasMoreContent // ignore: cast_nullable_to_non_nullable -as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable -as bool,isRefreshing: null == isRefreshing ? _self.isRefreshing : isRefreshing // ignore: cast_nullable_to_non_nullable -as bool,isInitialLoad: null == isInitialLoad ? _self.isInitialLoad : isInitialLoad // ignore: cast_nullable_to_non_nullable -as bool,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable -as String?,lastUpdated: freezed == lastUpdated ? _self.lastUpdated : lastUpdated // ignore: cast_nullable_to_non_nullable -as DateTime?,videoListSources: null == videoListSources ? _self._videoListSources : videoListSources // ignore: cast_nullable_to_non_nullable -as Map>,listOnlyVideoIds: null == listOnlyVideoIds ? _self._listOnlyVideoIds : listOnlyVideoIds // ignore: cast_nullable_to_non_nullable -as Set,totalVideoCount: freezed == totalVideoCount ? _self.totalVideoCount : totalVideoCount // ignore: cast_nullable_to_non_nullable -as int?,isFetchingTotalCount: null == isFetchingTotalCount ? _self.isFetchingTotalCount : isFetchingTotalCount // ignore: cast_nullable_to_non_nullable -as bool, - )); -} - - -} - -// dart format on diff --git a/mobile/lib/utils/device_memory_util.dart b/mobile/lib/utils/device_memory_util.dart index 98894c7342..e113bb0e8d 100644 --- a/mobile/lib/utils/device_memory_util.dart +++ b/mobile/lib/utils/device_memory_util.dart @@ -2,6 +2,7 @@ // ABOUTME: Used to prevent OOM crashes when rendering large images on low-memory devices import 'dart:ui' show Size; + import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:unified_logger/unified_logger.dart'; diff --git a/mobile/lib/utils/log_message_batcher.dart b/mobile/lib/utils/log_message_batcher.dart index effc2e81e9..0e0c655d34 100644 --- a/mobile/lib/utils/log_message_batcher.dart +++ b/mobile/lib/utils/log_message_batcher.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:collection'; + import 'package:unified_logger/unified_logger.dart'; /// Batches similar log messages and outputs summaries instead of individual messages diff --git a/mobile/lib/utils/proofmode_publishing_helpers.dart b/mobile/lib/utils/proofmode_publishing_helpers.dart index f8ff7a0512..523c854706 100644 --- a/mobile/lib/utils/proofmode_publishing_helpers.dart +++ b/mobile/lib/utils/proofmode_publishing_helpers.dart @@ -2,6 +2,7 @@ // ABOUTME: Extracts verification levels and creates Nostr tags from NativeProofData import 'dart:convert'; + import 'package:models/models.dart' show NativeProofData; /// Extract proof-verification-level from NativeProofData diff --git a/mobile/lib/widgets/library/drafts_tab.dart b/mobile/lib/widgets/library/drafts_tab.dart index 6438348104..999d52eb84 100644 --- a/mobile/lib/widgets/library/drafts_tab.dart +++ b/mobile/lib/widgets/library/drafts_tab.dart @@ -2,6 +2,7 @@ // ABOUTME: Lists saved video drafts with options to post, edit, duplicate, delete import 'dart:async'; + import 'package:divine_ui/divine_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/mobile/lib/widgets/library/pinch_zoom_grid.dart b/mobile/lib/widgets/library/pinch_zoom_grid.dart index c4b80695dc..66d0f6f4e5 100644 --- a/mobile/lib/widgets/library/pinch_zoom_grid.dart +++ b/mobile/lib/widgets/library/pinch_zoom_grid.dart @@ -14,12 +14,11 @@ import 'package:flutter/rendering.dart'; /// The [controller] must be handed to the built scroll view — [PinchZoomGrid] /// uses it to keep the same region of the grid on screen when the column /// count changes. -typedef PinchZoomGridBuilder = - Widget Function( - BuildContext context, - int columnCount, - ScrollController controller, - ); +typedef PinchZoomGridBuilder = Widget Function( + BuildContext context, + int columnCount, + ScrollController controller, +); /// Dispatched when a pinch on a [PinchZoomGrid] starts and ends. /// diff --git a/mobile/lib/widgets/profile/searchable_follow_list.dart b/mobile/lib/widgets/profile/searchable_follow_list.dart index fa5caa4050..7752de466c 100644 --- a/mobile/lib/widgets/profile/searchable_follow_list.dart +++ b/mobile/lib/widgets/profile/searchable_follow_list.dart @@ -10,8 +10,11 @@ import 'package:openvine/blocs/follow_list_search/follow_list_search_bloc.dart'; import 'package:openvine/l10n/l10n.dart'; /// Builds the row rendered for [pubkey] at [index] in a follow list. -typedef FollowListItemBuilder = - Widget Function(BuildContext context, String pubkey, int index); +typedef FollowListItemBuilder = Widget Function( + BuildContext context, + String pubkey, + int index, +); /// A pull-to-refresh user list with the design's search field pinned above it. /// diff --git a/mobile/lib/widgets/subtitle_editor/subtitle_editor_stage.dart b/mobile/lib/widgets/subtitle_editor/subtitle_editor_stage.dart index 6f7a2cfb61..504dfce2b2 100644 --- a/mobile/lib/widgets/subtitle_editor/subtitle_editor_stage.dart +++ b/mobile/lib/widgets/subtitle_editor/subtitle_editor_stage.dart @@ -19,18 +19,18 @@ import 'package:unified_logger/unified_logger.dart'; /// Loads the preview player's candidate sources into [controller]. @visibleForTesting -typedef SubtitlePreviewSourceLoader = - Future Function({ - required DivineVideoPlayerController controller, - required List sources, - required void Function(String message) log, - required bool Function() isLoadCurrent, - }); +typedef SubtitlePreviewSourceLoader = Future Function({ + required DivineVideoPlayerController controller, + required List sources, + required void Function(String message) log, + required bool Function() isLoadCurrent, +}); /// Initializes the preview player controller before source loading. @visibleForTesting -typedef SubtitlePreviewControllerInitializer = - Future Function(DivineVideoPlayerController controller); +typedef SubtitlePreviewControllerInitializer = Future Function( + DivineVideoPlayerController controller, +); Future _initializeSubtitlePreviewController( DivineVideoPlayerController controller, diff --git a/mobile/lib/widgets/video_editor/stop_motion/stop_motion_frame_commands.dart b/mobile/lib/widgets/video_editor/stop_motion/stop_motion_frame_commands.dart index 839bb3d690..269fc27002 100644 --- a/mobile/lib/widgets/video_editor/stop_motion/stop_motion_frame_commands.dart +++ b/mobile/lib/widgets/video_editor/stop_motion/stop_motion_frame_commands.dart @@ -50,7 +50,10 @@ bool commitStopMotionFrames( if (identical(frames, clip.stopMotionFrames)) return false; final updated = StopMotionFrameOps.clipWithFrames(clip, frames); - final newClips = [for (final c in state.clips) c.id == clipId ? updated : c]; + final newClips = [ + for (final c in state.clips) + if (c.id == clipId) updated else c, + ]; final rebasedMarkers = rebaseTimelineMarkersForClipState( oldClips: state.clips, newClips: newClips, diff --git a/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip.dart b/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip.dart index 5e657b4a64..783028d516 100644 --- a/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip.dart +++ b/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip.dart @@ -26,13 +26,12 @@ import 'package:openvine/widgets/video_editor/timeline_editor/strips/timeline_tr part 'video_editor_timeline_clip_strip_tiles.dart'; /// Callback reporting a trim change for a clip. -typedef ClipTrimCallback = - void Function({ - required String clipId, - required bool isStart, - required Duration trimStart, - required Duration trimEnd, - }); +typedef ClipTrimCallback = void Function({ + required String clipId, + required bool isStart, + required Duration trimStart, + required Duration trimEnd, +}); /// Callback reporting a reorder change — from index and to index. typedef ClipReorderCallback = void Function(int from, int to); diff --git a/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_overlay_strip.dart b/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_overlay_strip.dart index a96fbdf678..619a5993fa 100644 --- a/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_overlay_strip.dart +++ b/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_overlay_strip.dart @@ -22,32 +22,29 @@ import 'package:openvine/widgets/video_editor/timeline_editor/video_editor_timel /// [startTime] and [duration] are non-null when the resize extends /// beyond the original item boundary (overlays have no fixed content /// length so they can grow in either direction). -typedef OverlayTrimCallback = - void Function({ - required TimelineOverlayItem item, - required Duration startTime, - required Duration endTime, - required bool isStart, - }); +typedef OverlayTrimCallback = void Function({ + required TimelineOverlayItem item, + required Duration startTime, + required Duration endTime, + required bool isStart, +}); /// Callback reporting an item was moved to a new start time and row. /// /// When [insertAbove] is `true`, the item should keep the target row /// and existing overlapping items shift down. -typedef OverlayMoveCallback = - void Function({ - required TimelineOverlayItem item, - required Duration startTime, - required int row, - required bool insertAbove, - }); +typedef OverlayMoveCallback = void Function({ + required TimelineOverlayItem item, + required Duration startTime, + required int row, + required bool insertAbove, +}); /// Called on every drag-move frame with the live (snapped) start time. -typedef OverlayMovingCallback = - void Function({ - required TimelineOverlayItem item, - required Duration startTime, - }); +typedef OverlayMovingCallback = void Function({ + required TimelineOverlayItem item, + required Duration startTime, +}); /// A generic strip that displays [TimelineOverlayItem]s in rows. /// diff --git a/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_positioned_item.dart b/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_positioned_item.dart index 5bbcf6b13b..b20b28c30f 100644 --- a/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_positioned_item.dart +++ b/mobile/lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_positioned_item.dart @@ -395,15 +395,13 @@ class _TrimmableOverlayTileState extends State<_TrimmableOverlayTile> { : null; final posMs = _leftSnap.update(rawStartMs, snapPoints); - final clampedMs = - posMs.clamp( - 0, - math.max( - widget.totalDuration.inMilliseconds, - _rightSnap.originMs, - ), - ) - as int; + final clampedMs = posMs.clamp( + 0, + math.max( + widget.totalDuration.inMilliseconds, + _rightSnap.originMs, + ), + ) as int; final atMinTrim = (_rightSnap.originMs - clampedMs) < diff --git a/mobile/lib/widgets/video_feed_item/actions/share_sheet_message_input.dart b/mobile/lib/widgets/video_feed_item/actions/share_sheet_message_input.dart index ecf4032358..da6d84818a 100644 --- a/mobile/lib/widgets/video_feed_item/actions/share_sheet_message_input.dart +++ b/mobile/lib/widgets/video_feed_item/actions/share_sheet_message_input.dart @@ -49,13 +49,12 @@ class _MessageInput extends StatelessWidget { maxLines: 4, minLines: 1, maxLength: 500, - buildCounter: - ( - context, { - required currentLength, - required isFocused, - required maxLength, - }) => null, + buildCounter: ( + context, { + required currentLength, + required isFocused, + required maxLength, + }) => null, ), ), _SendButton(isSending: isSending, onTap: onSend), diff --git a/mobile/lib/widgets/video_metadata/modes/classic/video_metadata_classic_preview_thumbnail.dart b/mobile/lib/widgets/video_metadata/modes/classic/video_metadata_classic_preview_thumbnail.dart index a290e580ab..7fdb750941 100644 --- a/mobile/lib/widgets/video_metadata/modes/classic/video_metadata_classic_preview_thumbnail.dart +++ b/mobile/lib/widgets/video_metadata/modes/classic/video_metadata_classic_preview_thumbnail.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:divine_ui/divine_ui.dart'; import 'package:divine_video_player/divine_video_player.dart'; import 'package:flutter/material.dart'; diff --git a/mobile/lib/widgets/vine_cached_image.dart b/mobile/lib/widgets/vine_cached_image.dart index 3d1328b6d2..bba5e1e8d0 100644 --- a/mobile/lib/widgets/vine_cached_image.dart +++ b/mobile/lib/widgets/vine_cached_image.dart @@ -7,12 +7,17 @@ export 'package:openvine/utils/open_vine_image_cache.dart' show clearOpenVineImageCache, openVineImageCache; /// Signature used to build a loading placeholder. -typedef PlaceholderWidgetBuilder = - Widget Function(BuildContext context, String imageUrl); +typedef PlaceholderWidgetBuilder = Widget Function( + BuildContext context, + String imageUrl, +); /// Signature used to build an error widget. -typedef LoadingErrorWidgetBuilder = - Widget Function(BuildContext context, String imageUrl, Object error); +typedef LoadingErrorWidgetBuilder = Widget Function( + BuildContext context, + String imageUrl, + Object error, +); typedef ImageDimensionsResolved = void Function(int width, int height); diff --git a/mobile/mise.toml b/mobile/mise.toml index 98fe2c27cd..642e48df3e 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -1,5 +1,5 @@ [tools] -flutter = "3.44.9" +flutter = "3.47.2" # Android/Gradle builds need a JDK, and nothing else provisions one. 17 is what # the build actually targets: app/build.gradle.kts sets source/targetCompatibility # and jvmTarget to 17, and codemagic.yaml pins `java: 17` for every Android diff --git a/mobile/packages/background_uploader/analysis_options.yaml b/mobile/packages/background_uploader/analysis_options.yaml index 9df80aa490..45a8e763bc 100644 --- a/mobile/packages/background_uploader/analysis_options.yaml +++ b/mobile/packages/background_uploader/analysis_options.yaml @@ -1 +1,5 @@ +analyzer: + exclude: + - build/** + - android/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/background_uploader/pubspec.yaml b/mobile/packages/background_uploader/pubspec.yaml index d3af6e427c..9eff4ab436 100644 --- a/mobile/packages/background_uploader/pubspec.yaml +++ b/mobile/packages/background_uploader/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: equatable: ^2.0.8 diff --git a/mobile/packages/blurhash_service/analysis_options.yaml b/mobile/packages/blurhash_service/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/blurhash_service/analysis_options.yaml +++ b/mobile/packages/blurhash_service/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/blurhash_service/pubspec.yaml b/mobile/packages/blurhash_service/pubspec.yaml index 72b275fcb6..de13af795e 100644 --- a/mobile/packages/blurhash_service/pubspec.yaml +++ b/mobile/packages/blurhash_service/pubspec.yaml @@ -7,7 +7,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: divine_blurhash: diff --git a/mobile/packages/caption_generator/analysis_options.yaml b/mobile/packages/caption_generator/analysis_options.yaml index 9df80aa490..45a8e763bc 100644 --- a/mobile/packages/caption_generator/analysis_options.yaml +++ b/mobile/packages/caption_generator/analysis_options.yaml @@ -1 +1,5 @@ +analyzer: + exclude: + - build/** + - android/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/caption_generator/pubspec.yaml b/mobile/packages/caption_generator/pubspec.yaml index a31a81fb92..cd807b4e22 100644 --- a/mobile/packages/caption_generator/pubspec.yaml +++ b/mobile/packages/caption_generator/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: equatable: ^2.0.8 diff --git a/mobile/packages/divine_camera/analysis_options.yaml b/mobile/packages/divine_camera/analysis_options.yaml index 9df80aa490..8353d4c3ca 100644 --- a/mobile/packages/divine_camera/analysis_options.yaml +++ b/mobile/packages/divine_camera/analysis_options.yaml @@ -1 +1,7 @@ +analyzer: + exclude: + - build/** + - android/** + - ios/** + - macos/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/divine_camera/pubspec.yaml b/mobile/packages/divine_camera/pubspec.yaml index d3596af5da..3a07564f3d 100644 --- a/mobile/packages/divine_camera/pubspec.yaml +++ b/mobile/packages/divine_camera/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: equatable: ^2.0.8 diff --git a/mobile/packages/divine_device_attestation/analysis_options.yaml b/mobile/packages/divine_device_attestation/analysis_options.yaml index 203be55f5d..17bd12ebac 100644 --- a/mobile/packages/divine_device_attestation/analysis_options.yaml +++ b/mobile/packages/divine_device_attestation/analysis_options.yaml @@ -1,3 +1,7 @@ +analyzer: + exclude: + - build/** + - ios/** include: package:very_good_analysis/analysis_options.yaml # Additional information about this file can be found at diff --git a/mobile/packages/divine_device_attestation/pubspec.yaml b/mobile/packages/divine_device_attestation/pubspec.yaml index c148f7a26f..d578d8fe09 100644 --- a/mobile/packages/divine_device_attestation/pubspec.yaml +++ b/mobile/packages/divine_device_attestation/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: flutter: diff --git a/mobile/packages/divine_quick_actions/analysis_options.yaml b/mobile/packages/divine_quick_actions/analysis_options.yaml index 9df80aa490..b273948d08 100644 --- a/mobile/packages/divine_quick_actions/analysis_options.yaml +++ b/mobile/packages/divine_quick_actions/analysis_options.yaml @@ -1 +1,6 @@ +analyzer: + exclude: + - build/** + - android/** + - ios/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/divine_quick_actions/pubspec.yaml b/mobile/packages/divine_quick_actions/pubspec.yaml index 5617662d6c..7e84daa7d9 100644 --- a/mobile/packages/divine_quick_actions/pubspec.yaml +++ b/mobile/packages/divine_quick_actions/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: equatable: ^2.0.8 diff --git a/mobile/packages/divine_ui/analysis_options.yaml b/mobile/packages/divine_ui/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/divine_ui/analysis_options.yaml +++ b/mobile/packages/divine_ui/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/divine_ui/pubspec.yaml b/mobile/packages/divine_ui/pubspec.yaml index 41c2176f7d..683057da04 100644 --- a/mobile/packages/divine_ui/pubspec.yaml +++ b/mobile/packages/divine_ui/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: equatable: ^2.0.8 diff --git a/mobile/packages/divine_video_player/analysis_options.yaml b/mobile/packages/divine_video_player/analysis_options.yaml index 9df80aa490..45a8e763bc 100644 --- a/mobile/packages/divine_video_player/analysis_options.yaml +++ b/mobile/packages/divine_video_player/analysis_options.yaml @@ -1 +1,5 @@ +analyzer: + exclude: + - build/** + - android/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/divine_video_player/pubspec.yaml b/mobile/packages/divine_video_player/pubspec.yaml index 98a1822222..a07568df3e 100644 --- a/mobile/packages/divine_video_player/pubspec.yaml +++ b/mobile/packages/divine_video_player/pubspec.yaml @@ -9,7 +9,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: flutter: diff --git a/mobile/packages/iap_repository/analysis_options.yaml b/mobile/packages/iap_repository/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/iap_repository/analysis_options.yaml +++ b/mobile/packages/iap_repository/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/image_metadata_stripper/analysis_options.yaml b/mobile/packages/image_metadata_stripper/analysis_options.yaml index 9df80aa490..8353d4c3ca 100644 --- a/mobile/packages/image_metadata_stripper/analysis_options.yaml +++ b/mobile/packages/image_metadata_stripper/analysis_options.yaml @@ -1 +1,7 @@ +analyzer: + exclude: + - build/** + - android/** + - ios/** + - macos/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/image_metadata_stripper/pubspec.yaml b/mobile/packages/image_metadata_stripper/pubspec.yaml index e1f25c14bb..d8228d897e 100644 --- a/mobile/packages/image_metadata_stripper/pubspec.yaml +++ b/mobile/packages/image_metadata_stripper/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: flutter: diff --git a/mobile/packages/infinite_video_feed/analysis_options.yaml b/mobile/packages/infinite_video_feed/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/infinite_video_feed/analysis_options.yaml +++ b/mobile/packages/infinite_video_feed/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/infinite_video_feed/pubspec.yaml b/mobile/packages/infinite_video_feed/pubspec.yaml index b2672e38d6..cdae8ed171 100644 --- a/mobile/packages/infinite_video_feed/pubspec.yaml +++ b/mobile/packages/infinite_video_feed/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: divine_video_player: any diff --git a/mobile/packages/keycast_flutter/analysis_options.yaml b/mobile/packages/keycast_flutter/analysis_options.yaml index 3ea651f049..9eecc8742f 100644 --- a/mobile/packages/keycast_flutter/analysis_options.yaml +++ b/mobile/packages/keycast_flutter/analysis_options.yaml @@ -6,6 +6,8 @@ include: package:very_good_analysis/analysis_options.yaml analyzer: errors: avoid_catches_without_on_clauses: ignore + exclude: + - build/** linter: rules: diff --git a/mobile/packages/media_cache/analysis_options.yaml b/mobile/packages/media_cache/analysis_options.yaml index 670d9396b7..8aafbfea37 100644 --- a/mobile/packages/media_cache/analysis_options.yaml +++ b/mobile/packages/media_cache/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml \ No newline at end of file diff --git a/mobile/packages/media_cache/pubspec.yaml b/mobile/packages/media_cache/pubspec.yaml index c7ea3ba377..a385803f95 100644 --- a/mobile/packages/media_cache/pubspec.yaml +++ b/mobile/packages/media_cache/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: clock: ^1.1.2 diff --git a/mobile/packages/nostr_client/pubspec.yaml b/mobile/packages/nostr_client/pubspec.yaml index 3157397408..b46d2375b7 100644 --- a/mobile/packages/nostr_client/pubspec.yaml +++ b/mobile/packages/nostr_client/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: crypto: ^3.0.7 diff --git a/mobile/packages/nostr_key_manager/analysis_options.yaml b/mobile/packages/nostr_key_manager/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/nostr_key_manager/analysis_options.yaml +++ b/mobile/packages/nostr_key_manager/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/nostr_sdk/analysis_options.yaml b/mobile/packages/nostr_sdk/analysis_options.yaml index a5744c1cfb..e46654b9b2 100644 --- a/mobile/packages/nostr_sdk/analysis_options.yaml +++ b/mobile/packages/nostr_sdk/analysis_options.yaml @@ -1,3 +1,6 @@ +analyzer: + exclude: + - build/** include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at diff --git a/mobile/packages/nostr_sdk/pubspec.yaml b/mobile/packages/nostr_sdk/pubspec.yaml index 6e6f056b22..50c8d67a08 100644 --- a/mobile/packages/nostr_sdk/pubspec.yaml +++ b/mobile/packages/nostr_sdk/pubspec.yaml @@ -13,7 +13,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: flutter: diff --git a/mobile/packages/nostr_sdk/test/integration/temp_relay_lifetime_test.dart b/mobile/packages/nostr_sdk/test/integration/temp_relay_lifetime_test.dart index 14a6ff3b04..ac4e9d59c2 100644 --- a/mobile/packages/nostr_sdk/test/integration/temp_relay_lifetime_test.dart +++ b/mobile/packages/nostr_sdk/test/integration/temp_relay_lifetime_test.dart @@ -128,50 +128,46 @@ void main() { } group('sweepIdleTempRelays', () { - test( - 'closes the temp relays a publish opened once they go idle', - () async { - final targets = [for (var i = 0; i < 5; i++) await startRelay()]; - final urls = [for (final relay in targets) relay.url]; - final pool = buildPool(); - final event = await signedGiftWrap(pool); - - await pool.sendEventAwaitOk( - ['EVENT', event.toJson()], - eventId: event.id, - tempRelays: urls, - targetRelays: urls, - timeout: const Duration(seconds: 2), - ); - - // Every address got a socket — that is the behaviour #6585 bounds, not - // removes: NIP-17 requires publishing to the recipient's own relays. + test('closes the temp relays a publish opened once they go idle', () async { + final targets = [for (var i = 0; i < 5; i++) await startRelay()]; + final urls = [for (final relay in targets) relay.url]; + final pool = buildPool(); + final event = await signedGiftWrap(pool); + + await pool.sendEventAwaitOk( + ['EVENT', event.toJson()], + eventId: event.id, + tempRelays: urls, + targetRelays: urls, + timeout: const Duration(seconds: 2), + ); + + // Every address got a socket — that is the behaviour #6585 bounds, not + // removes: NIP-17 requires publishing to the recipient's own relays. + expect( + targets.every((relay) => relay.openSockets > 0), + isTrue, + reason: 'publish should reach every target', + ); + expect(pool.tempRelayUrls, hasLength(5)); + + pool.sweepIdleTempRelays(); + await _waitUntil( + () => targets.every((relay) => relay.openSockets == 0), + describe: () => + 'idle temp relays still open: ' + '${targets.where((r) => r.openSockets > 0).map((r) => r.url)}', + ); + + for (final relay in targets) { expect( - targets.every((relay) => relay.openSockets > 0), - isTrue, - reason: 'publish should reach every target', - ); - expect(pool.tempRelayUrls, hasLength(5)); - - pool.sweepIdleTempRelays(); - await _waitUntil( - () => targets.every((relay) => relay.openSockets == 0), - describe: () => - 'idle temp relays still open: ' - '${targets.where((r) => r.openSockets > 0).map((r) => r.url)}', + relay.openSockets, + isZero, + reason: 'idle temp relay ${relay.url} should have been closed', ); - - for (final relay in targets) { - expect( - relay.openSockets, - isZero, - reason: 'idle temp relay ${relay.url} should have been closed', - ); - } - expect(pool.tempRelayUrls, isEmpty); - }, - timeout: const Timeout(Duration(minutes: 2)), - ); + } + expect(pool.tempRelayUrls, isEmpty); + }, timeout: const Timeout(Duration(minutes: 2))); test( 'leaves a temp relay alone while it is still serving a subscription', @@ -209,32 +205,28 @@ void main() { }); group('removeAll', () { - test( - 'removeAll closes temp relays and stops the sweep', - () async { - final target = await startRelay(); - final pool = buildPool(); - final event = await signedGiftWrap(pool); - - await pool.sendEventAwaitOk( - ['EVENT', event.toJson()], - eventId: event.id, - tempRelays: [target.url], - targetRelays: [target.url], - timeout: const Duration(seconds: 2), - ); - expect(pool.tempRelayUrls, hasLength(1)); - - pool.removeAll(); - await _waitUntil( - () => target.openSockets == 0, - describe: () => 'removeAll left ${target.url} open', - ); - - expect(pool.tempRelayUrls, isEmpty); - expect(target.openSockets, isZero); - }, - timeout: const Timeout(Duration(minutes: 2)), - ); + test('removeAll closes temp relays and stops the sweep', () async { + final target = await startRelay(); + final pool = buildPool(); + final event = await signedGiftWrap(pool); + + await pool.sendEventAwaitOk( + ['EVENT', event.toJson()], + eventId: event.id, + tempRelays: [target.url], + targetRelays: [target.url], + timeout: const Duration(seconds: 2), + ); + expect(pool.tempRelayUrls, hasLength(1)); + + pool.removeAll(); + await _waitUntil( + () => target.openSockets == 0, + describe: () => 'removeAll left ${target.url} open', + ); + + expect(pool.tempRelayUrls, isEmpty); + expect(target.openSockets, isZero); + }, timeout: const Timeout(Duration(minutes: 2))); }); } diff --git a/mobile/packages/nostr_sdk/test/unit/nostr_connect_session_test.dart b/mobile/packages/nostr_sdk/test/unit/nostr_connect_session_test.dart index a79b907848..37eea7e352 100644 --- a/mobile/packages/nostr_sdk/test/unit/nostr_connect_session_test.dart +++ b/mobile/packages/nostr_sdk/test/unit/nostr_connect_session_test.dart @@ -485,35 +485,31 @@ void main() { ); }); - test( - 'addRelay excludes relays whose connect times out', - () async { - final primaryRelay = await TestRelayServer.start(); - final blackhole = await _BlackholeServer.start(); - final callbackUrl = blackhole.url; - addTearDown(primaryRelay.close); - addTearDown(blackhole.close); + test('addRelay excludes relays whose connect times out', () async { + final primaryRelay = await TestRelayServer.start(); + final blackhole = await _BlackholeServer.start(); + final callbackUrl = blackhole.url; + addTearDown(primaryRelay.close); + addTearDown(blackhole.close); - final session = NostrConnectSession(relays: [primaryRelay.url]); - addTearDown(session.dispose); + final session = NostrConnectSession(relays: [primaryRelay.url]); + addTearDown(session.dispose); - await session.start(); - await session.addRelay(callbackUrl); + await session.start(); + await session.addRelay(callbackUrl); - final failedPort = blackhole.port; - await blackhole.close(); - final callbackRelay = await TestRelayServer.start(port: failedPort); - addTearDown(callbackRelay.close); + final failedPort = blackhole.port; + await blackhole.close(); + final callbackRelay = await TestRelayServer.start(port: failedPort); + addTearDown(callbackRelay.close); - await session.addRelay(callbackUrl); - expect( - callbackRelay.connectionCount, - equals(1), - reason: 'timed-out relay attempts must not be retained as connected', - ); - }, - timeout: const Timeout(Duration(seconds: 20)), - ); + await session.addRelay(callbackUrl); + expect( + callbackRelay.connectionCount, + equals(1), + reason: 'timed-out relay attempts must not be retained as connected', + ); + }, timeout: const Timeout(Duration(seconds: 20))); test( 'addRelay disconnects a relay that connects after the session timeout', diff --git a/mobile/packages/nostr_sdk/test/unit/relay_pool_subscription_silence_probe_test.dart b/mobile/packages/nostr_sdk/test/unit/relay_pool_subscription_silence_probe_test.dart index a895ffdd70..9b0eadd9ba 100644 --- a/mobile/packages/nostr_sdk/test/unit/relay_pool_subscription_silence_probe_test.dart +++ b/mobile/packages/nostr_sdk/test/unit/relay_pool_subscription_silence_probe_test.dart @@ -139,11 +139,9 @@ void main() { final subId = subscribeToFeed(); await Future.delayed(Duration.zero); - expect( - nostr.relayPool.armedSilenceProbeSubscriptionIds, - [subId], - reason: 'the probe is armed while the load is live', - ); + expect(nostr.relayPool.armedSilenceProbeSubscriptionIds, [ + subId, + ], reason: 'the probe is armed while the load is live'); // The user left the screen inside the relay's round-trip time. nostr.unsubscribe(subId); diff --git a/mobile/packages/nostr_sdk/test/unit/web_socket_connection_manager_test.dart b/mobile/packages/nostr_sdk/test/unit/web_socket_connection_manager_test.dart index 431586cd54..e665f011aa 100644 --- a/mobile/packages/nostr_sdk/test/unit/web_socket_connection_manager_test.dart +++ b/mobile/packages/nostr_sdk/test/unit/web_socket_connection_manager_test.dart @@ -656,18 +656,20 @@ void main() { maxReconnectAttempts: 4, baseReconnectDelay: Duration(milliseconds: 1), maxReconnectDelay: Duration(milliseconds: 4), - reconnectBudget: Duration(milliseconds: 5), + reconnectBudget: Duration(milliseconds: 50), ), ); addTearDown(boundedManager.dispose); expect(await boundedManager.send('first'), isFalse); final firstSendAttempts = boundedManager.reconnectAttempts; - expect(firstSendAttempts, greaterThan(0)); + expect(firstSendAttempts, 4); + expect(mockFactory.createdChannels, hasLength(4)); expect(await boundedManager.send('second'), isFalse); expect(boundedManager.reconnectAttempts, firstSendAttempts); + expect(mockFactory.createdChannels, hasLength(8)); }); test('manager budget expiry reaches the error stream', () async { diff --git a/mobile/packages/permissions_service/analysis_options.yaml b/mobile/packages/permissions_service/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/permissions_service/analysis_options.yaml +++ b/mobile/packages/permissions_service/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/permissions_service/pubspec.yaml b/mobile/packages/permissions_service/pubspec.yaml index c60711c4fb..9d081a889d 100644 --- a/mobile/packages/permissions_service/pubspec.yaml +++ b/mobile/packages/permissions_service/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: none environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 resolution: workspace diff --git a/mobile/packages/sound_service/analysis_options.yaml b/mobile/packages/sound_service/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/sound_service/analysis_options.yaml +++ b/mobile/packages/sound_service/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/sound_service/pubspec.yaml b/mobile/packages/sound_service/pubspec.yaml index 2abda7ba14..5b5a8adf1b 100644 --- a/mobile/packages/sound_service/pubspec.yaml +++ b/mobile/packages/sound_service/pubspec.yaml @@ -8,7 +8,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: audio_session: ^0.2.1 diff --git a/mobile/packages/tv_static_effect/analysis_options.yaml b/mobile/packages/tv_static_effect/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/tv_static_effect/analysis_options.yaml +++ b/mobile/packages/tv_static_effect/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/packages/tv_static_effect/lib/src/tv_static_noise.dart b/mobile/packages/tv_static_effect/lib/src/tv_static_noise.dart index 5fe91c5831..9a5d3dac95 100644 --- a/mobile/packages/tv_static_effect/lib/src/tv_static_noise.dart +++ b/mobile/packages/tv_static_effect/lib/src/tv_static_noise.dart @@ -77,7 +77,7 @@ class _TvStaticNoiseState extends State _elapsed = 0; _frame = -1; } else if (!_ticker.isActive) { - unawaited(_ticker.start()); + _ticker.start(); } } diff --git a/mobile/packages/tv_static_effect/pubspec.yaml b/mobile/packages/tv_static_effect/pubspec.yaml index 617b0f269c..618a1b1b06 100644 --- a/mobile/packages/tv_static_effect/pubspec.yaml +++ b/mobile/packages/tv_static_effect/pubspec.yaml @@ -6,7 +6,7 @@ resolution: workspace environment: sdk: ^3.11.0 - flutter: ^3.41.1 + flutter: ^3.47.0 dependencies: flutter: diff --git a/mobile/packages/unified_logger/analysis_options.yaml b/mobile/packages/unified_logger/analysis_options.yaml index 9df80aa490..054e568aa5 100644 --- a/mobile/packages/unified_logger/analysis_options.yaml +++ b/mobile/packages/unified_logger/analysis_options.yaml @@ -1 +1,4 @@ +analyzer: + exclude: + - build/** include: package:very_good_analysis/analysis_options.yaml diff --git a/mobile/pre_build_ios.sh b/mobile/pre_build_ios.sh index df549733af..c02b7e27f9 100755 --- a/mobile/pre_build_ios.sh +++ b/mobile/pre_build_ios.sh @@ -1,6 +1,6 @@ #!/bin/bash # ABOUTME: Pre-action of the shared Runner scheme: repairs the generated Swift package floor, syncs CocoaPods. -# ABOUTME: Runs no Flutter command; plugin injection would reset that floor to iOS 13. +# ABOUTME: Runs no Flutter command; plugin injection can reset that floor below iOS 16. set -e @@ -12,33 +12,18 @@ cd "$SCRIPT_DIR" # Never run a Flutter command here. Plugin injection rewrites # ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift -# at Flutter's default .iOS("13.0"), which every plugin needing 15/16 then -# fails against. Repair it in place, without invoking Flutter. +# at Flutter's default iOS floor, which can be lower than plugins require. +# Repair it in place, without invoking Flutter. # # This cannot rescue the build it runs in. Xcode emits "Resolve Package Graph" -# before it runs scheme pre-actions, so a build that starts at 13.0 still fails; +# before it runs scheme pre-actions, so a build that starts too low still fails; # measured 2026-09-06. What this buys is that the failure stops repeating: the # next build reads 16.0 and succeeds. Run `flutter build ios --config-only` # after a terminal Flutter command to skip the wasted first build. # See .claude/rules/ios_build_troubleshooting.md, Cause 3. SWIFT_PACKAGE_MANIFEST="ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift" if [ -f "$SWIFT_PACKAGE_MANIFEST" ]; then - if grep -Eq '\.iOS\(("13\.0"|\.v13)\)' "$SWIFT_PACKAGE_MANIFEST"; then - SWIFT_PACKAGE_TMP="$(mktemp "${SWIFT_PACKAGE_MANIFEST}.XXXXXX")" - trap 'rm -f "$SWIFT_PACKAGE_TMP"' EXIT - sed \ - -e 's/\.iOS("13\.0")/.iOS("16.0")/g' \ - -e 's/\.iOS(\.v13)/.iOS("16.0")/g' \ - "$SWIFT_PACKAGE_MANIFEST" > "$SWIFT_PACKAGE_TMP" - mv "$SWIFT_PACKAGE_TMP" "$SWIFT_PACKAGE_MANIFEST" - trap - EXIT - echo "✅ Raised generated Swift package deployment target to iOS 16.0" - fi - - if ! grep -Eq '\.iOS\(("16\.0"|\.v16)\)' "$SWIFT_PACKAGE_MANIFEST"; then - echo "❌ ERROR: Generated Swift package does not target iOS 16.0" - exit 1 - fi + ruby scripts/ensure_ios_swift_package_floor.rb "$SWIFT_PACKAGE_MANIFEST" fi # Navigate to iOS directory diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 08ccf2e158..f8304854f7 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -971,16 +971,8 @@ packages: description: flutter source: sdk version: "0.0.0" - freezed: - dependency: "direct dev" - description: - name: freezed - sha256: "8599ba37236328ff6f97269ecce875d251a367eb2929c9bbf9b811b2ce56c74e" - url: "https://pub.dev" - source: hosted - version: "3.2.6-dev.1" freezed_annotation: - dependency: "direct main" + dependency: transitive description: name: freezed_annotation sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" @@ -1265,10 +1257,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.20.3" io: dependency: transitive description: @@ -1393,10 +1385,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.20" material_color_utilities: dependency: transitive description: @@ -2398,26 +2390,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" + sha256: ca578dc12bb8b2f40b67b7d3bd2fac4f31c01a6ff7130a14e2597b919934507f url: "https://pub.dev" source: hosted - version: "1.31.0" + version: "1.31.1" test_api: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" test_core: dependency: transitive description: name: test_core - sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" + sha256: d2e98ec12998368dc59ddd47ab709f2cd55acd6b66dc7db764455a44082f4bc5 url: "https://pub.dev" source: hosted - version: "0.6.17" + version: "0.6.18" timezone: dependency: transitive description: @@ -2566,10 +2558,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.2" very_good_analysis: dependency: "direct dev" description: @@ -2771,5 +2763,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=3.12.0 <3.16.0" - flutter: ">=3.44.0 <4.0.0" + dart: ">=3.13.0 <3.16.0" + flutter: ">=3.47.0 <4.0.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 4534114aa2..53f97b6157 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -19,8 +19,8 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.22+820 environment: - sdk: ^3.12.0 - flutter: ^3.44.0 + sdk: ^3.13.0 + flutter: ^3.47.0 workspace: - packages/analytics @@ -126,7 +126,6 @@ dependencies: flutter_bloc: ^9.0.0 bloc_concurrency: ^0.3.0 provider: ^6.1.5+1 - freezed_annotation: ^3.0.0 json_annotation: ^4.9.0 rxdart: ^0.28.0 @@ -442,7 +441,6 @@ dev_dependencies: riverpod_generator: ^4.0.4 # custom_lint: ^0.6.4 # Temporarily disabled due to rxdart version conflict # riverpod_lint: ^2.3.10 # Depends on custom_lint - freezed: ^3.0.0 json_serializable: ^6.8.0 yaml: any golden_toolkit: ^0.15.0 diff --git a/mobile/scripts/baseline/async_safety_counts.txt b/mobile/scripts/baseline/async_safety_counts.txt index ea701723b9..9a2b58aba7 100644 --- a/mobile/scripts/baseline/async_safety_counts.txt +++ b/mobile/scripts/baseline/async_safety_counts.txt @@ -19,8 +19,7 @@ discarded_futures|lib/blocs/supporter/supporter_cubit.dart 3 discarded_futures|lib/blocs/video_editor/transition_boundary/transition_boundary_cubit.dart 1 discarded_futures|lib/blocs/video_recorder/video_recorder_bloc.dart 5 discarded_futures|lib/features/feature_flags/providers/feature_flag_providers.dart 1 -discarded_futures|lib/features/people_lists/view/add_people_to_list_screen.dart 2 -discarded_futures|lib/features/people_lists/view/create_people_list_page.dart 1 +discarded_futures|lib/features/people_lists/view/add_people_to_list_screen.dart 1 discarded_futures|lib/main.dart 1 discarded_futures|lib/mixins/grid_prefetch_mixin.dart 2 discarded_futures|lib/mixins/video_prefetch_mixin.dart 1 @@ -68,7 +67,7 @@ discarded_futures|lib/screens/badges/badge_editor_screen.dart 1 discarded_futures|lib/screens/badges/badges_screen.dart 1 discarded_futures|lib/screens/blossom_settings_screen.dart 1 discarded_futures|lib/screens/category_gallery_screen.dart 2 -discarded_futures|lib/screens/comments/comments_screen.dart 3 +discarded_futures|lib/screens/comments/comments_screen.dart 1 discarded_futures|lib/screens/comments/widgets/pending_video_reply_tile.dart 1 discarded_futures|lib/screens/comments/widgets/video_comment_player.dart 2 discarded_futures|lib/screens/content_filters_screen.dart 2 @@ -80,17 +79,15 @@ discarded_futures|lib/screens/explore/explore_view.dart 3 discarded_futures|lib/screens/explore/tabs/explore_lists_tab.dart 4 discarded_futures|lib/screens/explore/tabs/featured_videos_tab.dart 2 discarded_futures|lib/screens/feed/feed_settings_menu.dart 1 -discarded_futures|lib/screens/feed/pooled_fullscreen_video_feed_screen.dart 2 +discarded_futures|lib/screens/feed/pooled_fullscreen_video_feed_screen.dart 1 discarded_futures|lib/screens/feed/video_feed_page/feed_error_widget.dart 1 discarded_futures|lib/screens/hashtag_feed_screen.dart 2 discarded_futures|lib/screens/inbox/conversation/conversation_page.dart 1 discarded_futures|lib/screens/inbox/conversation/conversation_view.dart 6 discarded_futures|lib/screens/inbox/conversation/widgets/collaborator_invite_card.dart 2 discarded_futures|lib/screens/inbox/conversation/widgets/message_bubble.dart 3 -discarded_futures|lib/screens/inbox/conversation/widgets/reactions_detail_sheet.dart 1 -discarded_futures|lib/screens/inbox/conversation/widgets/reactions_row.dart 1 discarded_futures|lib/screens/inbox/conversation/widgets/video_link_preview_cubit.dart 1 -discarded_futures|lib/screens/inbox/inbox_view.dart 3 +discarded_futures|lib/screens/inbox/inbox_view.dart 2 discarded_futures|lib/screens/inbox/message_requests/message_requests_view.dart 1 discarded_futures|lib/screens/inbox/message_requests/request_preview_page.dart 1 discarded_futures|lib/screens/library_screen.dart 1 @@ -119,13 +116,13 @@ discarded_futures|lib/screens/subtitle_editor/subtitle_editor_screen.dart 3 discarded_futures|lib/screens/user_list_people_screen.dart 5 discarded_futures|lib/screens/verify/verify_screen.dart 1 discarded_futures|lib/screens/video_detail_screen.dart 6 -discarded_futures|lib/screens/video_editor/video_audio_editor_timing_screen.dart 7 +discarded_futures|lib/screens/video_editor/video_audio_editor_timing_screen.dart 6 discarded_futures|lib/screens/video_editor/video_editor_screen.dart 6 -discarded_futures|lib/screens/video_editor/voice_over_recorder_screen.dart 2 +discarded_futures|lib/screens/video_editor/voice_over_recorder_screen.dart 1 discarded_futures|lib/screens/video_metadata/video_metadata_cover_screen.dart 2 discarded_futures|lib/screens/video_metadata/video_metadata_edit_screen.dart 1 discarded_futures|lib/screens/video_metadata/video_metadata_preview_screen.dart 1 -discarded_futures|lib/screens/web_auth_screen.dart 2 +discarded_futures|lib/screens/web_auth_screen.dart 1 discarded_futures|lib/services/analytics_service.dart 1 discarded_futures|lib/services/auth/nostr_connect_coordinator.dart 2 discarded_futures|lib/services/background_activity_manager.dart 1 @@ -163,17 +160,13 @@ discarded_futures|lib/startup/upload_failure_listener.dart 1 discarded_futures|lib/utils/deferred_login_options_navigator.dart 3 discarded_futures|lib/widgets/add_to_list_dialog.dart 1 discarded_futures|lib/widgets/app_lifecycle_handler.dart 1 -discarded_futures|lib/widgets/audio_waveform.dart 1 discarded_futures|lib/widgets/auth/forgot_password_dialog.dart 1 -discarded_futures|lib/widgets/auth_back_button.dart 1 discarded_futures|lib/widgets/badges/awarded_badge_card.dart 1 -discarded_futures|lib/widgets/branded_loading_indicator.dart 1 discarded_futures|lib/widgets/bug_report_dialog.dart 1 discarded_futures|lib/widgets/categories_tab.dart 1 discarded_futures|lib/widgets/classic_vines_tab.dart 1 -discarded_futures|lib/widgets/community_suggest/community_suggest_sheet.dart 3 +discarded_futures|lib/widgets/community_suggest/community_suggest_sheet.dart 2 discarded_futures|lib/widgets/composable_video_grid.dart 1 -discarded_futures|lib/widgets/content_warning.dart 1 discarded_futures|lib/widgets/delete_account_dialog.dart 2 discarded_futures|lib/widgets/developer_options/shorebird_patch_section.dart 2 discarded_futures|lib/widgets/feature_request_dialog.dart 1 @@ -184,21 +177,17 @@ discarded_futures|lib/widgets/geo_blocking_gate.dart 1 discarded_futures|lib/widgets/global_error_widget.dart 1 discarded_futures|lib/widgets/image_attachment_picker.dart 1 discarded_futures|lib/widgets/library/clips_tab.dart 2 -discarded_futures|lib/widgets/library/drag_select_region.dart 1 -discarded_futures|lib/widgets/library/pinch_zoom_grid.dart 1 discarded_futures|lib/widgets/library/sounds_tab.dart 2 discarded_futures|lib/widgets/linkified_text/linkified_text_navigation.dart 4 discarded_futures|lib/widgets/new_videos_tab.dart 1 -discarded_futures|lib/widgets/notification_badge.dart 2 +discarded_futures|lib/widgets/notification_badge.dart 1 discarded_futures|lib/widgets/owned_list_card.dart 1 discarded_futures|lib/widgets/popular_videos_tab.dart 1 -discarded_futures|lib/widgets/profile/more_sheet/more_sheet_content.dart 1 discarded_futures|lib/widgets/profile/profile_action_buttons_widget.dart 2 discarded_futures|lib/widgets/profile/profile_actions_sheet/profile_actions_sheet_content.dart 1 discarded_futures|lib/widgets/profile/profile_collabs_grid.dart 1 discarded_futures|lib/widgets/profile/profile_grid.dart 14 discarded_futures|lib/widgets/profile/profile_header_identity.dart 5 -discarded_futures|lib/widgets/profile/profile_header_media.dart 1 discarded_futures|lib/widgets/profile/profile_header_widget.dart 2 discarded_futures|lib/widgets/profile/profile_liked_grid.dart 1 discarded_futures|lib/widgets/profile/profile_reposts_grid.dart 1 @@ -207,49 +196,39 @@ discarded_futures|lib/widgets/profile/profile_videos_grid.dart 3 discarded_futures|lib/widgets/profile/searchable_follow_list.dart 1 discarded_futures|lib/widgets/profile_editor/username_status_indicator.dart 1 discarded_futures|lib/widgets/report_content_confirmation.dart 1 -discarded_futures|lib/widgets/report_content_dialog.dart 4 +discarded_futures|lib/widgets/report_content_dialog.dart 2 discarded_futures|lib/widgets/save_original_progress_sheet.dart 1 discarded_futures|lib/widgets/show_edit_dialog_for_video.dart 1 -discarded_futures|lib/widgets/stop_motion/stop_motion_player.dart 2 -discarded_futures|lib/widgets/subtitle_editor/subtitle_editor_stage.dart 1 +discarded_futures|lib/widgets/stop_motion/stop_motion_player.dart 1 discarded_futures|lib/widgets/support_capped_text_field.dart 2 discarded_futures|lib/widgets/trending_hashtags_section.dart 1 -discarded_futures|lib/widgets/upload_failure_sheet.dart 3 +discarded_futures|lib/widgets/upload_failure_sheet.dart 2 discarded_futures|lib/widgets/user_avatar.dart 1 -discarded_futures|lib/widgets/user_picker_sheet.dart 3 +discarded_futures|lib/widgets/user_picker_sheet.dart 2 discarded_futures|lib/widgets/video_clip/video_clip_preview.dart 3 -discarded_futures|lib/widgets/video_editor/audio_editor/audio_list_tile.dart 1 discarded_futures|lib/widgets/video_editor/audio_editor/audio_selection_bottom_sheet.dart 1 discarded_futures|lib/widgets/video_editor/chroma_key/chroma_key_controls.dart 1 discarded_futures|lib/widgets/video_editor/chroma_key/chroma_keyed_video.dart 1 -discarded_futures|lib/widgets/video_editor/main_editor/video_editor_canvas.dart 22 +discarded_futures|lib/widgets/video_editor/main_editor/video_editor_canvas.dart 20 discarded_futures|lib/widgets/video_editor/main_editor/video_editor_main_overlay_actions.dart 2 -discarded_futures|lib/widgets/video_editor/timeline_editor/controls/video_editor_caption_custom_style_sheet.dart 1 -discarded_futures|lib/widgets/video_editor/timeline_editor/controls/video_editor_caption_preset_sheet.dart 1 -discarded_futures|lib/widgets/video_editor/timeline_editor/controls/video_editor_captions_sheet.dart 1 -discarded_futures|lib/widgets/video_editor/timeline_editor/controls/video_editor_layer_animation_sheet.dart 1 -discarded_futures|lib/widgets/video_editor/timeline_editor/controls/video_editor_transition_sheet.dart 1 discarded_futures|lib/widgets/video_editor/timeline_editor/strips/video_editor_stop_motion_frame_strip.dart 4 discarded_futures|lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip.dart 6 discarded_futures|lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip_tiles.dart 2 discarded_futures|lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_overlay_strip.dart 1 discarded_futures|lib/widgets/video_editor/timeline_editor/strips/video_editor_timeline_positioned_item.dart 2 discarded_futures|lib/widgets/video_editor/timeline_editor/timeline_snap_controller.dart 1 -discarded_futures|lib/widgets/video_editor/timeline_editor/video_editor_timeline.dart 1 discarded_futures|lib/widgets/video_editor/timeline_editor/video_editor_timeline_volume.dart 1 discarded_futures|lib/widgets/video_editor/timeline_editor/video_editor_volume_mute_toggle.dart 1 discarded_futures|lib/widgets/video_editor/video_editor_color_picker_sheet.dart 2 discarded_futures|lib/widgets/video_editor/video_editor_processing_overlay.dart 1 -discarded_futures|lib/widgets/video_editor/video_editor_scaffold.dart 2 discarded_futures|lib/widgets/video_feed_item/actions/comment_action_button.dart 1 discarded_futures|lib/widgets/video_feed_item/actions/help_classify_action_button.dart 1 discarded_futures|lib/widgets/video_feed_item/actions/like_action_button.dart 1 discarded_futures|lib/widgets/video_feed_item/actions/report_action_button.dart 1 discarded_futures|lib/widgets/video_feed_item/actions/repost_action_button.dart 1 -discarded_futures|lib/widgets/video_feed_item/actions/share_action_button.dart 12 +discarded_futures|lib/widgets/video_feed_item/actions/share_action_button.dart 11 discarded_futures|lib/widgets/video_feed_item/audio_attribution_row.dart 1 discarded_futures|lib/widgets/video_feed_item/collaborator_avatar_row.dart 3 -discarded_futures|lib/widgets/video_feed_item/double_tap_heart_overlay.dart 1 discarded_futures|lib/widgets/video_feed_item/feed_playback_toggles_pill.dart 2 discarded_futures|lib/widgets/video_feed_item/feed_videos.dart 3 discarded_futures|lib/widgets/video_feed_item/inline_comment_composer_bar.dart 1 @@ -259,22 +238,18 @@ discarded_futures|lib/widgets/video_feed_item/metadata/metadata_sounds_section.d discarded_futures|lib/widgets/video_feed_item/metadata/metadata_tags_section.dart 1 discarded_futures|lib/widgets/video_feed_item/metadata/metadata_user_chips.dart 2 discarded_futures|lib/widgets/video_feed_item/metadata/video_reposters_cubit.dart 1 -discarded_futures|lib/widgets/video_feed_item/paused_video_overlay.dart 1 -discarded_futures|lib/widgets/video_feed_item/reaction_overlay.dart 1 -discarded_futures|lib/widgets/video_feed_item/reel_dm_reply_bar.dart 5 +discarded_futures|lib/widgets/video_feed_item/reel_dm_reply_bar.dart 4 discarded_futures|lib/widgets/video_feed_item/video_feed_item.dart 3 discarded_futures|lib/widgets/video_feed_item/video_follow_button.dart 1 discarded_futures|lib/widgets/video_feed_item/video_interactions_restriction_listener.dart 1 discarded_futures|lib/widgets/video_metadata/modes/classic/video_metadata_classic_preview_thumbnail.dart 3 discarded_futures|lib/widgets/video_metadata/modes/edit/video_metadata_edit_bottom_bar.dart 1 -discarded_futures|lib/widgets/video_metadata/video_metadata_tags_selector.dart 1 discarded_futures|lib/widgets/video_recorder/clip_delete_snackbar.dart 1 -discarded_futures|lib/widgets/video_recorder/modes/capture/video_recorder_shutter_flash.dart 1 discarded_futures|lib/widgets/video_recorder/video_recorder_library_button.dart 2 discarded_futures|lib/widgets/video_recorder/video_recorder_mode_selector.dart 2 discarded_futures|lib/widgets/video_recorder/video_recorder_navigation.dart 1 discarded_futures|lib/widgets/video_reply_parent_link.dart 1 -discarded_futures|lib/widgets/vine_bottom_nav.dart 5 +discarded_futures|lib/widgets/vine_bottom_nav.dart 2 discarded_futures|lib/widgets/watermark_download_progress_sheet.dart 1 discarded_futures|test/blocs/clips_library/clips_library_bloc_test.dart 4 discarded_futures|test/blocs/comments/comment_composer/comment_composer_bloc_test.dart 1 @@ -352,9 +327,7 @@ discarded_futures|test/screens/auth/email_verification_screen_test.dart 1 discarded_futures|test/screens/auth/invite_gate_screen_test.dart 4 discarded_futures|test/screens/auth/invite_protected_create_account_screen_test.dart 1 discarded_futures|test/screens/feed/feed_mode_switch_test.dart 2 -discarded_futures|test/screens/feed/pooled_fullscreen_video_feed_screen_test.dart 1 discarded_futures|test/screens/inbox/bloc/new_message_search_bloc_test.dart 1 -discarded_futures|test/screens/minor_account_review_screen_test.dart 1 discarded_futures|test/screens/safety_settings_screen_test.dart 6 discarded_futures|test/screens/search_blocklist_filter_test.dart 6 discarded_futures|test/screens/search_results/view/search_results_view_test.dart 5 @@ -365,7 +338,6 @@ discarded_futures|test/screens/search_results/widgets/search_filter_sheet_test.d discarded_futures|test/screens/search_results/widgets/tags_section_test.dart 1 discarded_futures|test/screens/search_results/widgets/videos_section_test.dart 1 discarded_futures|test/screens/settings/account/change_email_screen_test.dart 1 -discarded_futures|test/screens/video_recorder_screen_test.dart 1 discarded_futures|test/services/auth/oauth_session_coordinator_test.dart 6 discarded_futures|test/services/auth_service_bootstrap_relay_list_test.dart 2 discarded_futures|test/services/auth_service_signout_cleanup_test.dart 1 @@ -373,7 +345,7 @@ discarded_futures|test/services/crossposting_api_client_test.dart 1 discarded_futures|test/services/hashtag_filtering_integration_test.dart 1 discarded_futures|test/services/media_viewer_auth_service_test.dart 3 discarded_futures|test/services/network/rate_limiter_test.dart 1 -discarded_futures|test/services/page_load_observer_test.dart 2 +discarded_futures|test/services/page_load_observer_test.dart 1 discarded_futures|test/services/saved_sounds_service_test.dart 9 discarded_futures|test/services/subscribed_list_video_cache_test.dart 1 discarded_futures|test/services/subscription_manager_cache_test.dart 1 @@ -397,7 +369,6 @@ discarded_futures|test/unit/services/video_event_service_force_refresh_test.dart discarded_futures|test/unit/services/video_event_service_reset_resubscribe_test.dart 2 discarded_futures|test/unit/subscription_manager_tdd_test.dart 2 discarded_futures|test/utils/pause_aware_modals_test.dart 6 -discarded_futures|test/widgets/feed_tuning/feed_tuning_swipe_overlay_test.dart 1 discarded_futures|test/widgets/find_people_sheet_test.dart 1 discarded_futures|test/widgets/profile/profile_actions_sheet/profile_actions_sheet_content_test.dart 1 discarded_futures|test/widgets/save_original_progress_sheet_test.dart 1 @@ -423,7 +394,7 @@ unawaited_futures|lib/screens/auth/secure_account_screen.dart 1 unawaited_futures|lib/screens/curated_list_feed_screen.dart 2 unawaited_futures|lib/screens/feed/video_feed_page.dart 1 unawaited_futures|lib/screens/inbox/conversation/conversation_view.dart 2 -unawaited_futures|lib/screens/inbox/conversation/widgets/reactions_detail_sheet.dart 3 +unawaited_futures|lib/screens/inbox/conversation/widgets/reactions_detail_sheet.dart 2 unawaited_futures|lib/screens/inbox/inbox_view.dart 2 unawaited_futures|lib/screens/settings/support_center_screen.dart 1 unawaited_futures|lib/screens/verify/verify_screen.dart 1 @@ -444,7 +415,6 @@ unawaited_futures|lib/widgets/add_to_list_dialog.dart 1 unawaited_futures|lib/widgets/app_lifecycle_handler.dart 1 unawaited_futures|lib/widgets/image_attachment_picker.dart 1 unawaited_futures|lib/widgets/library/sounds_tab.dart 1 -unawaited_futures|lib/widgets/report_content_dialog.dart 1 unawaited_futures|lib/widgets/upload_failure_sheet.dart 1 unawaited_futures|lib/widgets/video_editor/draw_editor/video_editor_draw_bottom_bar.dart 1 unawaited_futures|lib/widgets/video_editor/main_editor/video_editor_canvas.dart 2 @@ -460,10 +430,8 @@ unawaited_futures|test/providers/editor_background_work_test.dart 1 unawaited_futures|test/providers/explore_active_video_test.dart 2 unawaited_futures|test/providers/subtitle_visibility_test.dart 1 unawaited_futures|test/providers/video_playback_stop_integration_test.dart 5 -unawaited_futures|test/screens/comments/comments_screen_test.dart 1 unawaited_futures|test/screens/settings/nip05_settings_screen_test.dart 1 unawaited_futures|test/screens/settings/storage/storage_management_view_test.dart 10 -unawaited_futures|test/screens/video_recorder_screen_test.dart 1 unawaited_futures|test/services/auth_service_data_cleanup_ordering_test.dart 1 unawaited_futures|test/services/auth_service_identity_change_preservation_test.dart 1 unawaited_futures|test/services/auth_service_invite_session_cleanup_test.dart 1 @@ -473,7 +441,6 @@ unawaited_futures|test/services/auth_service_oauth_local_signing_test.dart 1 unawaited_futures|test/services/auth_service_oauth_recovery_test.dart 1 unawaited_futures|test/services/auth_service_signing_mismatch_test.dart 1 unawaited_futures|test/services/back_button_handler_test.dart 1 -unawaited_futures|test/services/page_load_observer_test.dart 5 unawaited_futures|test/services/profile_editing_race_condition_test.dart 6 unawaited_futures|test/services/profile_editing_test.dart 1 unawaited_futures|test/services/saved_sounds_service_test.dart 4 @@ -485,7 +452,6 @@ unawaited_futures|test/unit/services/video_event_service_infinite_scroll_test.da unawaited_futures|test/unit/services/video_event_service_reset_resubscribe_test.dart 1 unawaited_futures|test/unit/services/video_event_service_search_test.dart 2 unawaited_futures|test/unit/subscription_manager_tdd_test.dart 1 -unawaited_futures|test/utils/await_push_transition_test.dart 1 unawaited_futures|test/widgets/community_suggest/community_suggest_sheet_test.dart 1 unawaited_futures|test/widgets/composable_video_grid_test.dart 1 unawaited_futures|test/widgets/metadata_sounds_section_test.dart 1 diff --git a/mobile/scripts/baseline/service_god_file_sizes.txt b/mobile/scripts/baseline/service_god_file_sizes.txt index c9805d5875..da6f85523b 100644 --- a/mobile/scripts/baseline/service_god_file_sizes.txt +++ b/mobile/scripts/baseline/service_god_file_sizes.txt @@ -6,8 +6,8 @@ # This hard service baseline is authoritative over the broad advisory check for # service god-file ceilings. # Regenerate after shrinking/removing: UPDATE_BASELINE=1 bash scripts/check_service_god_file_ceiling.sh -lib/services/auth_service.dart 4529 -lib/services/curated_list_service.dart 1847 -lib/services/upload_manager.dart 1958 -lib/services/video_event_publisher.dart 2295 -lib/services/video_event_service.dart 6500 +lib/services/auth_service.dart 4509 +lib/services/curated_list_service.dart 1841 +lib/services/upload_manager.dart 1957 +lib/services/video_event_publisher.dart 2294 +lib/services/video_event_service.dart 6499 diff --git a/mobile/scripts/ensure_ios_swift_package_floor.rb b/mobile/scripts/ensure_ios_swift_package_floor.rb new file mode 100644 index 0000000000..9801f795f0 --- /dev/null +++ b/mobile/scripts/ensure_ios_swift_package_floor.rb @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby +# ABOUTME: Raises generated Flutter Swift packages to Divine's iOS 16 floor. +# ABOUTME: Used by Xcode and Codemagic so Flutter-version changes cannot drift. + +require 'rubygems' + +MINIMUM_IOS_VERSION = Gem::Version.new('16.0') + +abort('usage: ensure_ios_swift_package_floor.rb [...]') if ARGV.empty? + +ARGV.each do |path| + contents = File.read(path) + matched_platform = false + updated = contents.gsub(/\.iOS\((?:"(\d+(?:\.\d+)*)"|\.v(\d+))\)/) do |declaration| + matched_platform = true + version = Gem::Version.new(Regexp.last_match(1) || Regexp.last_match(2)) + version < MINIMUM_IOS_VERSION ? '.iOS("16.0")' : declaration + end + + unless matched_platform + if updated.match?(/^\s*platforms:\s*\[/) + abort("expected #{path} platforms block to declare an iOS target") + end + + updated = updated.sub( + /(let package = Package\(\n\s*name: "[^"]+",\n)/, + "\\1 platforms: [\n .iOS(\"16.0\")\n ],\n", + ) + end + + ios_match = updated.match(/\.iOS\((?:"(\d+(?:\.\d+)*)"|\.v(\d+))\)/) + ios_version = ios_match && (ios_match[1] || ios_match[2]) + if ios_version.nil? || Gem::Version.new(ios_version) < MINIMUM_IOS_VERSION + abort("expected #{path} to target iOS 16.0 or newer") + end + + if updated == contents + puts "#{path} already targets iOS #{ios_version}" + next + end + + File.write(path, updated) + puts "raised #{path} to iOS 16.0" +end diff --git a/mobile/test/assets/sticker_manifest_consistency_test.dart b/mobile/test/assets/sticker_manifest_consistency_test.dart index 2f891a4914..00d38323b6 100644 --- a/mobile/test/assets/sticker_manifest_consistency_test.dart +++ b/mobile/test/assets/sticker_manifest_consistency_test.dart @@ -57,9 +57,9 @@ void main() { for (final locale in expectedLocales) { test('"$locale" strings cover every sticker with non-blank text', () { - final strings = - json.decode(File('$i18nDirectory/$locale.json').readAsStringSync()) - as Map; + final strings = json.decode( + File('$i18nDirectory/$locale.json').readAsStringSync(), + ) as Map; expect( strings.keys.toSet(), diff --git a/mobile/test/blocs/clips_library/clips_library_bloc_test.dart b/mobile/test/blocs/clips_library/clips_library_bloc_test.dart index 7667c0799a..75d4f6aca2 100644 --- a/mobile/test/blocs/clips_library/clips_library_bloc_test.dart +++ b/mobile/test/blocs/clips_library/clips_library_bloc_test.dart @@ -1589,13 +1589,12 @@ void main() { }); final output = File('${tempDir.path}/rendered.mp4') ..writeAsStringSync('rendered'); - StopMotionRenderService.assembleOverride = - ({ - required frames, - required aspectRatio, - frameRate = StopMotionRenderService.defaultFrameRate, - String? taskId, - }) async => output.path; + StopMotionRenderService.assembleOverride = ({ + required frames, + required aspectRatio, + frameRate = StopMotionRenderService.defaultFrameRate, + String? taskId, + }) async => output.path; StopMotionRenderService.probeDurationOverride = (_) async => null; when( () => mockGallerySaveService.saveVideoToGallery(any()), diff --git a/mobile/test/blocs/crossposting_settings/crossposting_settings_cubit_test.dart b/mobile/test/blocs/crossposting_settings/crossposting_settings_cubit_test.dart index a20a31f9ef..6b69464b65 100644 --- a/mobile/test/blocs/crossposting_settings/crossposting_settings_cubit_test.dart +++ b/mobile/test/blocs/crossposting_settings/crossposting_settings_cubit_test.dart @@ -423,38 +423,25 @@ void main() { ); final invalidCallbacks = { - 'custom scheme': - 'divine://divine.video/app/callback?connection=connected&platform=instagram', - 'http scheme': - 'http://divine.video/app/callback?connection=connected&platform=instagram', - 'lookalike host': - 'https://divine.video.evil.test/app/callback?connection=connected&platform=instagram', - 'subdomain host': - 'https://www.divine.video/app/callback?connection=connected&platform=instagram', - 'wrong path': - 'https://divine.video/app/callback/extra?connection=connected&platform=instagram', - 'unknown platform': - 'https://divine.video/app/callback?connection=connected&platform=bluesky', + 'custom scheme': 'divine://divine.video/app/callback?connection=connected&platform=instagram', + 'http scheme': 'http://divine.video/app/callback?connection=connected&platform=instagram', + 'lookalike host': 'https://divine.video.evil.test/app/callback?connection=connected&platform=instagram', + 'subdomain host': 'https://www.divine.video/app/callback?connection=connected&platform=instagram', + 'wrong path': 'https://divine.video/app/callback/extra?connection=connected&platform=instagram', + 'unknown platform': 'https://divine.video/app/callback?connection=connected&platform=bluesky', 'mismatched supported platform': 'https://divine.video/app/callback?connection=connected&platform=x', - 'unknown connection': - 'https://divine.video/app/callback?connection=pending&platform=instagram', + 'unknown connection': 'https://divine.video/app/callback?connection=pending&platform=instagram', 'missing connection': 'https://divine.video/app/callback?platform=instagram', 'missing platform': 'https://divine.video/app/callback?connection=connected', - 'duplicate connection': - 'https://divine.video/app/callback?connection=connected&connection=failed&platform=instagram', - 'duplicate platform': - 'https://divine.video/app/callback?connection=connected&platform=instagram&platform=x', - 'explicit port': - 'https://divine.video:8443/app/callback?connection=connected&platform=instagram', - 'user info': - 'https://oauth-user@divine.video/app/callback?connection=connected&platform=instagram', - 'fragment': - 'https://divine.video/app/callback?connection=connected&platform=instagram#unexpected', - 'duplicate reason': - 'https://divine.video/app/callback?connection=failed&platform=instagram&reason=provider_denied&reason=other', + 'duplicate connection': 'https://divine.video/app/callback?connection=connected&connection=failed&platform=instagram', + 'duplicate platform': 'https://divine.video/app/callback?connection=connected&platform=instagram&platform=x', + 'explicit port': 'https://divine.video:8443/app/callback?connection=connected&platform=instagram', + 'user info': 'https://oauth-user@divine.video/app/callback?connection=connected&platform=instagram', + 'fragment': 'https://divine.video/app/callback?connection=connected&platform=instagram#unexpected', + 'duplicate reason': 'https://divine.video/app/callback?connection=failed&platform=instagram&reason=provider_denied&reason=other', }; for (final invalid in invalidCallbacks.entries) { diff --git a/mobile/test/blocs/dm/conversation/collaborator_invite_actions_cubit_test.dart b/mobile/test/blocs/dm/conversation/collaborator_invite_actions_cubit_test.dart index 94a4f18450..1083f6d8ef 100644 --- a/mobile/test/blocs/dm/conversation/collaborator_invite_actions_cubit_test.dart +++ b/mobile/test/blocs/dm/conversation/collaborator_invite_actions_cubit_test.dart @@ -30,8 +30,7 @@ void main() { const invite = CollaboratorInvite( messageId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', - videoAddress: - '34236:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:skate-loop', + videoAddress: '34236:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:skate-loop', videoKind: 34236, creatorPubkey: creatorPubkey, videoDTag: 'skate-loop', diff --git a/mobile/test/blocs/dm/conversation/conversation_bloc_test.dart b/mobile/test/blocs/dm/conversation/conversation_bloc_test.dart index 299cbd0abc..6e082c59d1 100644 --- a/mobile/test/blocs/dm/conversation/conversation_bloc_test.dart +++ b/mobile/test/blocs/dm/conversation/conversation_bloc_test.dart @@ -512,8 +512,7 @@ void main() { senderPubkey: recipientPubkey, content: 'Reply message', createdAt: 1700000100, - giftWrapId: - '8888888888888888888888888888888888888888888888888888888888888888', + giftWrapId: '8888888888888888888888888888888888888888888888888888888888888888', ); controller.add([testMessage, secondMessage]); controller.close(); @@ -600,8 +599,7 @@ void main() { senderPubkey: recipientPubkey, content: 'Reply message', createdAt: 1700000100, - giftWrapId: - '8888888888888888888888888888888888888888888888888888888888888888', + giftWrapId: '8888888888888888888888888888888888888888888888888888888888888888', ); messagesController.add([testMessage, secondMessage]); await _waitForConversationState( @@ -1700,8 +1698,7 @@ void main() { senderPubkey: senderPubkey, content: 'Should be ignored', createdAt: 1700000200, - giftWrapId: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + giftWrapId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), ]); controller1.close(); @@ -1714,8 +1711,7 @@ void main() { senderPubkey: recipientPubkey, content: 'New subscription message', createdAt: 1700000300, - giftWrapId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + giftWrapId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', ), ]); controller2.close(); diff --git a/mobile/test/blocs/dm/conversation/conversation_state_test.dart b/mobile/test/blocs/dm/conversation/conversation_state_test.dart index dbe0898b1f..6eb47870b7 100644 --- a/mobile/test/blocs/dm/conversation/conversation_state_test.dart +++ b/mobile/test/blocs/dm/conversation/conversation_state_test.dart @@ -212,8 +212,7 @@ void main() { final failedSibling = _outgoingDm( id: 'rumor-d', content: 'trio', - recipientPubkey: - '4444444444444444444444444444444444444444444444444444444444444444', + recipientPubkey: '4444444444444444444444444444444444444444444444444444444444444444', recipientWrap: OutgoingWrapStatus.failed, ); final persistedWinner = _message(id: 'rumor-b', content: 'trio'); @@ -325,8 +324,7 @@ void main() { _outgoingDm(id: 'rumor-c', recipientPubkey: _recipientC), _outgoingDm( id: 'rumor-d', - recipientPubkey: - '4444444444444444444444444444444444444444444444444444444444444444', + recipientPubkey: '4444444444444444444444444444444444444444444444444444444444444444', recipientWrap: OutgoingWrapStatus.failed, ), ], @@ -395,14 +393,12 @@ void main() { _outgoingDm(id: 'rumor-c', recipientPubkey: _recipientC), _outgoingDm( id: 'rumor-d', - recipientPubkey: - '4444444444444444444444444444444444444444444444444444444444444444', + recipientPubkey: '4444444444444444444444444444444444444444444444444444444444444444', recipientWrap: OutgoingWrapStatus.failed, ), _outgoingDm( id: 'rumor-e', - recipientPubkey: - '5555555555555555555555555555555555555555555555555555555555555555', + recipientPubkey: '5555555555555555555555555555555555555555555555555555555555555555', recipientWrap: OutgoingWrapStatus.sent, selfWrap: OutgoingWrapStatus.failed, ), diff --git a/mobile/test/blocs/invite_status/invite_status_cubit_test.dart b/mobile/test/blocs/invite_status/invite_status_cubit_test.dart index a5f183981c..20f883b5b4 100644 --- a/mobile/test/blocs/invite_status/invite_status_cubit_test.dart +++ b/mobile/test/blocs/invite_status/invite_status_cubit_test.dart @@ -28,8 +28,7 @@ void main() { InviteCode( code: 'HN4P-QR56', claimed: true, - claimedBy: - 'aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa1', + claimedBy: 'aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa1', ), ], ); @@ -349,39 +348,40 @@ void main() { }, ); - test('Keycast status loads when delayed RPC signer becomes ready', () async { - final authSessions = StreamController(); - addTearDown(authSessions.close); - when( - () => mockInviteApiClient.getInviteStatus(), - ).thenAnswer((_) async => testStatus); + test( + 'Keycast status loads when delayed RPC signer becomes ready', + () async { + final authSessions = StreamController(); + addTearDown(authSessions.close); + when( + () => mockInviteApiClient.getInviteStatus(), + ).thenAnswer((_) async => testStatus); - final cubit = buildCubit( - initialAuthSession: const InviteStatusAuthSession( - accountId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', - isSignerReady: false, - ), - authSessionStream: authSessions.stream, - )..start(); - addTearDown(cubit.close); + final cubit = buildCubit( + initialAuthSession: const InviteStatusAuthSession( + accountId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + isSignerReady: false, + ), + authSessionStream: authSessions.stream, + )..start(); + addTearDown(cubit.close); - verifyNever(() => mockInviteApiClient.getInviteStatus()); + verifyNever(() => mockInviteApiClient.getInviteStatus()); - authSessions.add( - const InviteStatusAuthSession( - accountId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', - isSignerReady: true, - ), - ); - await cubit.stream.firstWhere( - (state) => state.status == InviteStatusLoadingStatus.loaded, - ); + authSessions.add( + const InviteStatusAuthSession( + accountId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + isSignerReady: true, + ), + ); + await cubit.stream.firstWhere( + (state) => state.status == InviteStatusLoadingStatus.loaded, + ); - verify(() => mockInviteApiClient.getInviteStatus()).called(1); - expect(cubit.state.inviteStatus, equals(testStatus)); - }); + verify(() => mockInviteApiClient.getInviteStatus()).called(1); + expect(cubit.state.inviteStatus, equals(testStatus)); + }, + ); test('waiting for auth falls through to error after timeout', () { fakeAsync((async) { @@ -538,8 +538,7 @@ void main() { authSessions.add( const InviteStatusAuthSession( - accountId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + accountId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', isSignerReady: true, ), ); @@ -589,8 +588,7 @@ void main() { ); authSessions.add( const InviteStatusAuthSession( - accountId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + accountId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', isSignerReady: true, ), ); @@ -640,8 +638,7 @@ void main() { authSessions.add( const InviteStatusAuthSession( - accountId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + accountId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', isSignerReady: true, ), ); diff --git a/mobile/test/blocs/my_profile/my_profile_bloc_test.dart b/mobile/test/blocs/my_profile/my_profile_bloc_test.dart index 70d3cfde37..8a362d8a2b 100644 --- a/mobile/test/blocs/my_profile/my_profile_bloc_test.dart +++ b/mobile/test/blocs/my_profile/my_profile_bloc_test.dart @@ -257,13 +257,11 @@ void main() { setUp: () { final cachedProfile = createTestProfile( nip05: '_@alice.divine.video', - eventId: - 'cached12345678901234567890123456789012345678901234567890123456', + eventId: 'cached12345678901234567890123456789012345678901234567890123456', ); final freshProfile = createTestProfile( nip05: '_@alice.divine.video', - eventId: - 'fresh123456789012345678901234567890123456789012345678901234567', + eventId: 'fresh123456789012345678901234567890123456789012345678901234567', ); when( () => mockProfileRepository.getCachedProfile(pubkey: testPubkey), @@ -734,13 +732,11 @@ void main() { setUp: () { final cachedProfile = createTestProfile( displayName: 'Cached Name', - eventId: - 'cached12345678901234567890123456789012345678901234567890123456', + eventId: 'cached12345678901234567890123456789012345678901234567890123456', ); final streamProfile = createTestProfile( displayName: 'Stream Name', - eventId: - 'stream12345678901234567890123456789012345678901234567890123456', + eventId: 'stream12345678901234567890123456789012345678901234567890123456', ); when( () => mockProfileRepository.getCachedProfile(pubkey: testPubkey), @@ -818,18 +814,15 @@ void main() { setUp: () { final cachedProfile = createTestProfile( displayName: 'Cached Name', - eventId: - 'cached12345678901234567890123456789012345678901234567890123456', + eventId: 'cached12345678901234567890123456789012345678901234567890123456', ); final oldName = createTestProfile( displayName: 'Old Name', - eventId: - 'oldnam12345678901234567890123456789012345678901234567890123456', + eventId: 'oldnam12345678901234567890123456789012345678901234567890123456', ); final newName = createTestProfile( displayName: 'New Name', - eventId: - 'fresh123456789012345678901234567890123456789012345678901234567', + eventId: 'fresh123456789012345678901234567890123456789012345678901234567', ); when( () => mockProfileRepository.getCachedProfile(pubkey: testPubkey), @@ -1216,8 +1209,7 @@ void main() { 'does not preserve a stale profile for a different pubkey', seed: () => MyProfileUpdated( profile: createTestProfile( - pubkey: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + pubkey: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', displayName: 'Wrong User', ), ), diff --git a/mobile/test/blocs/other_profile/other_profile_bloc_test.dart b/mobile/test/blocs/other_profile/other_profile_bloc_test.dart index e1310b73d3..a4392dff4d 100644 --- a/mobile/test/blocs/other_profile/other_profile_bloc_test.dart +++ b/mobile/test/blocs/other_profile/other_profile_bloc_test.dart @@ -307,12 +307,10 @@ void main() { 'emits [loading with cache, loaded fresh] when fresh fetch succeeds', setUp: () { final cachedProfile = createTestProfile( - eventId: - 'cached12345678901234567890123456789012345678901234567890123456', + eventId: 'cached12345678901234567890123456789012345678901234567890123456', ); final freshProfile = createTestProfile( - eventId: - 'fresh123456789012345678901234567890123456789012345678901234567', + eventId: 'fresh123456789012345678901234567890123456789012345678901234567', ); when( () => mockProfileRepository.getCachedProfile(pubkey: testPubkey), @@ -558,12 +556,10 @@ void main() { 'emits [loading with current, loaded fresh] when refresh succeeds', setUp: () { final cachedProfile = createTestProfile( - eventId: - 'cached12345678901234567890123456789012345678901234567890123456', + eventId: 'cached12345678901234567890123456789012345678901234567890123456', ); final freshProfile = createTestProfile( - eventId: - 'fresh123456789012345678901234567890123456789012345678901234567', + eventId: 'fresh123456789012345678901234567890123456789012345678901234567', ); when( () => mockProfileRepository.getCachedProfile(pubkey: testPubkey), @@ -575,8 +571,7 @@ void main() { build: createBloc, seed: () => OtherProfileLoaded( profile: createTestProfile( - eventId: - 'seed1234567890123456789012345678901234567890123456789012345678', + eventId: 'seed1234567890123456789012345678901234567890123456789012345678', ), isFresh: true, ), @@ -654,8 +649,7 @@ void main() { 'emits loaded fresh when refresh succeeds', setUp: () { final freshProfile = createTestProfile( - eventId: - 'fresh123456789012345678901234567890123456789012345678901234567', + eventId: 'fresh123456789012345678901234567890123456789012345678901234567', ); when( () => mockProfileRepository.fetchFreshProfile(pubkey: testPubkey), @@ -664,8 +658,7 @@ void main() { build: createBloc, seed: () => OtherProfileLoading( profile: createTestProfile( - eventId: - 'loading12345678901234567890123456789012345678901234567890123456', + eventId: 'loading12345678901234567890123456789012345678901234567890123456', ), ), act: (bloc) => bloc.add(const OtherProfileRefreshRequested()), @@ -686,8 +679,7 @@ void main() { 'preserves profile from error state during refresh', setUp: () { final freshProfile = createTestProfile( - eventId: - 'fresh123456789012345678901234567890123456789012345678901234567', + eventId: 'fresh123456789012345678901234567890123456789012345678901234567', ); when( () => mockProfileRepository.fetchFreshProfile(pubkey: testPubkey), @@ -697,8 +689,7 @@ void main() { seed: () => OtherProfileError( errorType: OtherProfileErrorType.networkError, profile: createTestProfile( - eventId: - 'error123456789012345678901234567890123456789012345678901234567', + eventId: 'error123456789012345678901234567890123456789012345678901234567', ), ), act: (bloc) => bloc.add(const OtherProfileRefreshRequested()), diff --git a/mobile/test/blocs/profile_editor/profile_editor_bloc_test.dart b/mobile/test/blocs/profile_editor/profile_editor_bloc_test.dart index 89795977d3..7e06fc1332 100644 --- a/mobile/test/blocs/profile_editor/profile_editor_bloc_test.dart +++ b/mobile/test/blocs/profile_editor/profile_editor_bloc_test.dart @@ -106,8 +106,7 @@ void main() { displayName: testDisplayName, rawData: const {}, createdAt: DateTime.now(), - eventId: - 'fallback12345678901234567890123456789012345678901234567890123456', + eventId: 'fallback12345678901234567890123456789012345678901234567890123456', ), ); registerFallbackValue(_FakeFile()); @@ -1183,8 +1182,7 @@ void main() { picture: testPicture, rawData: const {}, createdAt: DateTime.now(), - eventId: - 'nip05evt567890123456789012345678901234567890123456789012345678', + eventId: 'nip05evt567890123456789012345678901234567890123456789012345678', ), ), ), diff --git a/mobile/test/blocs/share_sheet/share_sheet_bloc_test.dart b/mobile/test/blocs/share_sheet/share_sheet_bloc_test.dart index 5d336b9841..df0bae7ca6 100644 --- a/mobile/test/blocs/share_sheet/share_sheet_bloc_test.dart +++ b/mobile/test/blocs/share_sheet/share_sheet_bloc_test.dart @@ -443,8 +443,7 @@ void main() { bloc.add( const ShareSheetRecipientToggled( ShareableUser( - pubkey: - 'zzzz56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'zzzz56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', displayName: 'Picked Stranger', ), ), @@ -508,8 +507,7 @@ void main() { bloc.add( const ShareSheetRecipientToggled( ShareableUser( - pubkey: - 'zzzz56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'zzzz56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', displayName: 'Picked Stranger', ), ), @@ -578,8 +576,7 @@ void main() { build: createBloc, act: (bloc) async { const picked = ShareableUser( - pubkey: - 'zzzz56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'zzzz56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', displayName: 'Picked Stranger', ); bloc.add(const ShareSheetContactsLoadRequested()); @@ -651,13 +648,11 @@ void main() { build: createBloc, act: (bloc) async { const firstPicked = ShareableUser( - pubkey: - 'yyyy56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'yyyy56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', displayName: 'Picked First', ); const secondPicked = ShareableUser( - pubkey: - 'zzzz56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'zzzz56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', displayName: 'Picked Second', ); bloc.add(const ShareSheetContactsLoadRequested()); diff --git a/mobile/test/blocs/video_editor/clip_editor/clip_editor_bloc_test.dart b/mobile/test/blocs/video_editor/clip_editor/clip_editor_bloc_test.dart index d146fc20a5..c115b3e677 100644 --- a/mobile/test/blocs/video_editor/clip_editor/clip_editor_bloc_test.dart +++ b/mobile/test/blocs/video_editor/clip_editor/clip_editor_bloc_test.dart @@ -2444,12 +2444,11 @@ void main() { blocTest( 'keeps the original clip when the render fails', build: () => buildBloc( - bakeChromaKey: - ({ - required sourceClip, - required chromaKey, - required renderId, - }) async => throw Exception('render failed'), + bakeChromaKey: ({ + required sourceClip, + required chromaKey, + required renderId, + }) async => throw Exception('render failed'), ), seed: () => ClipEditorState(clips: [_createClip()]), act: (bloc) => bloc.add( @@ -2562,12 +2561,11 @@ void main() { blocTest( 'restores the pre-key footage without rendering', build: () => buildBloc( - bakeChromaKey: - ({ - required sourceClip, - required chromaKey, - required renderId, - }) async => throw StateError('removal must not render'), + bakeChromaKey: ({ + required sourceClip, + required chromaKey, + required renderId, + }) async => throw StateError('removal must not render'), deferFileCleanup: (paths) => removeCleanupPaths.addAll(paths.whereType()), ), @@ -3078,12 +3076,11 @@ void main() { blocTest( 'emits failure and reports a Reportable on a render error', build: () => buildBloc( - transformClip: - ({ - required sourceClip, - required transform, - required renderId, - }) async => throw StateError('transform render failed'), + transformClip: ({ + required sourceClip, + required transform, + required renderId, + }) async => throw StateError('transform render failed'), ), seed: () => ClipEditorState(clips: [_createClipWithFile()]), act: (bloc) => bloc.add( @@ -3121,12 +3118,11 @@ void main() { final queued = []; final clip = _createClipWithFile(); final bloc = buildBloc( - transformClip: - ({ - required sourceClip, - required transform, - required renderId, - }) => completer.future, + transformClip: ({ + required sourceClip, + required transform, + required renderId, + }) => completer.future, deferFileCleanup: (paths) => queued.addAll(paths.whereType()), ); @@ -3169,12 +3165,11 @@ void main() { final completer = Completer(); final queued = []; final bloc = buildBloc( - transformClip: - ({ - required sourceClip, - required transform, - required renderId, - }) => completer.future, + transformClip: ({ + required sourceClip, + required transform, + required renderId, + }) => completer.future, deferFileCleanup: (paths) => queued.addAll(paths.whereType()), ); @@ -4283,9 +4278,11 @@ void main() { blocTest( 'discards the result when the source clip is removed mid-render', build: () => buildBloc( - flattenClipForLibrary: - ({required clip, required renderId, overlays}) => - flattenCompleter.future, + flattenClipForLibrary: ({ + required clip, + required renderId, + overlays, + }) => flattenCompleter.future, saveClipToLibrary: recordSave, ), seed: () => ClipEditorState(clips: twoClips), @@ -4313,9 +4310,11 @@ void main() { blocTest( 'drops a second request while one is in flight (no duplicate copy)', build: () => buildBloc( - flattenClipForLibrary: - ({required clip, required renderId, overlays}) => - flattenCompleter.future, + flattenClipForLibrary: ({ + required clip, + required renderId, + overlays, + }) => flattenCompleter.future, saveClipToLibrary: recordSave, ), seed: () => ClipEditorState(clips: twoClips), @@ -4337,9 +4336,11 @@ void main() { blocTest( 'marks the in-flight clip while the re-encode runs', build: () => buildBloc( - flattenClipForLibrary: - ({required clip, required renderId, overlays}) => - flattenCompleter.future, + flattenClipForLibrary: ({ + required clip, + required renderId, + overlays, + }) => flattenCompleter.future, saveClipToLibrary: recordSave, ), seed: () => ClipEditorState(clips: twoClips), @@ -4417,9 +4418,11 @@ void main() { blocTest( 'deletes the flattened file when the source clip was removed', build: () => buildBloc( - flattenClipForLibrary: - ({required clip, required renderId, overlays}) => - flattenCompleter.future, + flattenClipForLibrary: ({ + required clip, + required renderId, + overlays, + }) => flattenCompleter.future, cleanupFlattenedClip: recordCleanup, saveClipToLibrary: recordSave, ), diff --git a/mobile/test/blocs/video_feed/video_feed_bloc_test.dart b/mobile/test/blocs/video_feed/video_feed_bloc_test.dart index dc2f650dd1..48c2211cde 100644 --- a/mobile/test/blocs/video_feed/video_feed_bloc_test.dart +++ b/mobile/test/blocs/video_feed/video_feed_bloc_test.dart @@ -2257,8 +2257,7 @@ void main() { videos: [ createTestVideo( 'original-event-id', - pubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + pubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', vineId: 'shared-d-tag', createdAt: 2000, ), @@ -2317,8 +2316,7 @@ void main() { videos: [ createTestVideo( 'author-a-event', - pubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + pubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', vineId: 'shared-d-tag', createdAt: 2000, ), diff --git a/mobile/test/blocs/video_recorder/video_recorder_bloc_test.dart b/mobile/test/blocs/video_recorder/video_recorder_bloc_test.dart index fc6baa694e..078b574d33 100644 --- a/mobile/test/blocs/video_recorder/video_recorder_bloc_test.dart +++ b/mobile/test/blocs/video_recorder/video_recorder_bloc_test.dart @@ -32,6 +32,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:sound_service/sound_service.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart'; + import '../../mocks/mock_path_provider_platform.dart'; class _MockCameraService extends Mock implements CameraService {} diff --git a/mobile/test/builders/nostr_event_builder.dart b/mobile/test/builders/nostr_event_builder.dart index d76720579e..9ff81abe00 100644 --- a/mobile/test/builders/nostr_event_builder.dart +++ b/mobile/test/builders/nostr_event_builder.dart @@ -2,6 +2,7 @@ // ABOUTME: Supports all event types used in OpenVine with proper tag structures import 'dart:convert'; + import 'package:nostr_sdk/nostr_sdk.dart'; /// Builder class for creating test Nostr events diff --git a/mobile/test/config/bug_report_config_test.dart b/mobile/test/config/bug_report_config_test.dart index 11435c31a1..1016cdc532 100644 --- a/mobile/test/config/bug_report_config_test.dart +++ b/mobile/test/config/bug_report_config_test.dart @@ -171,8 +171,7 @@ void main() { // adds no triage value worth the risk of a partial leak. const cases = { 'macos home account name': ( - input: - "PathNotFoundException: path = '/Users/mjbradley/Library/Preferences/app.plist'", + input: "PathNotFoundException: path = '/Users/mjbradley/Library/Preferences/app.plist'", forbidden: 'mjbradley', ), 'macos home directory terminal': ( @@ -183,23 +182,19 @@ void main() { // space must redact whole. Stopping at the space would leave the // filename fragment beside the marker, a partial leak. 'macos home spaced filename quoted': ( - input: - "FileSystemException: path = '/Users/mjbradley/Music/My Song.m4a'", + input: "FileSystemException: path = '/Users/mjbradley/Music/My Song.m4a'", forbidden: 'Song', ), 'ios container spaced filename quoted': ( - input: - "FileSystemException: path = '/var/mobile/Containers/Data/Application/1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D/tmp/My Song.m4a'", + input: "FileSystemException: path = '/var/mobile/Containers/Data/Application/1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D/tmp/My Song.m4a'", forbidden: 'Song', ), 'macos external volume spaced filename quoted': ( - input: - "FileSystemException: path = '/Volumes/Backup Drive/Music/My Song.m4a'", + input: "FileSystemException: path = '/Volumes/Backup Drive/Music/My Song.m4a'", forbidden: 'My Song', ), 'linux home account name': ( - input: - "FileSystemException: Cannot open, path = '/home/mbradley/notes.txt'", + input: "FileSystemException: Cannot open, path = '/home/mbradley/notes.txt'", forbidden: 'mbradley', ), 'windows home account name': ( @@ -207,18 +202,15 @@ void main() { forbidden: 'mbradley', ), 'ios container user filename': ( - input: - "FileSystemException: path = '/var/mobile/Containers/Data/Application/1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D/Documents/draft_audio_imports/d1/1699999999000_My_Song.m4a'", + input: "FileSystemException: path = '/var/mobile/Containers/Data/Application/1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D/Documents/draft_audio_imports/d1/1699999999000_My_Song.m4a'", forbidden: 'My_Song', ), 'ios container install uuid': ( - input: - "FileSystemException: path = '/var/mobile/Containers/Data/Application/1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D/Documents/x.m4a'", + input: "FileSystemException: path = '/var/mobile/Containers/Data/Application/1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D/Documents/x.m4a'", forbidden: '1A2B3C4D', ), 'android app-data user filename': ( - input: - '/data/user/0/video.divine.app/cache/1699999999000_My_Song.m4a (No such file)', + input: '/data/user/0/video.divine.app/cache/1699999999000_My_Song.m4a (No such file)', forbidden: 'My_Song', ), 'android shared-storage user filename': ( @@ -226,8 +218,7 @@ void main() { forbidden: 'My_Song', ), 'macos container user filename via home root': ( - input: - "path = '/Users/mjbradley/Library/Containers/video.divine.app/Data/Documents/draft_audio_imports/d1/1699999999000_My_Song.m4a'", + input: "path = '/Users/mjbradley/Library/Containers/video.divine.app/Data/Documents/draft_audio_imports/d1/1699999999000_My_Song.m4a'", forbidden: 'My_Song', ), }; diff --git a/mobile/test/dao/user_profiles_dao_test.dart b/mobile/test/dao/user_profiles_dao_test.dart index 35a2175d59..6a3029fd97 100644 --- a/mobile/test/dao/user_profiles_dao_test.dart +++ b/mobile/test/dao/user_profiles_dao_test.dart @@ -2,6 +2,7 @@ // ABOUTME: Tests database operations, upsert behavior, and stream reactivity import 'dart:io'; + import 'package:db_client/db_client.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/mobile/test/extensions/modal_pop_extension_test.dart b/mobile/test/extensions/modal_pop_extension_test.dart index d638171a95..8cd9348f52 100644 --- a/mobile/test/extensions/modal_pop_extension_test.dart +++ b/mobile/test/extensions/modal_pop_extension_test.dart @@ -65,11 +65,9 @@ void main() { ), ); - unawaited( - Navigator.of(hostContext).push( - MaterialPageRoute( - builder: (_) => const Scaffold(body: Text('covered screen')), - ), + Navigator.of(hostContext).push( + MaterialPageRoute( + builder: (_) => const Scaffold(body: Text('covered screen')), ), ); await tester.pumpAndSettle(); diff --git a/mobile/test/features/oauth/app_oauth_callback_test.dart b/mobile/test/features/oauth/app_oauth_callback_test.dart index 306a9fd659..a19e881346 100644 --- a/mobile/test/features/oauth/app_oauth_callback_test.dart +++ b/mobile/test/features/oauth/app_oauth_callback_test.dart @@ -120,12 +120,11 @@ void main() { test('reports a dismissed session as null', () async { final uri = await launchAppOAuth( Uri.parse('https://verifier.divine.video/auth/twitter/start'), - authenticate: - ({ - required String url, - required String callbackUrlScheme, - required FlutterWebAuth2Options options, - }) async => throw PlatformException(code: 'CANCELED'), + authenticate: ({ + required String url, + required String callbackUrlScheme, + required FlutterWebAuth2Options options, + }) async => throw PlatformException(code: 'CANCELED'), ); expect(uri, isNull); @@ -135,12 +134,11 @@ void main() { await expectLater( () => launchAppOAuth( Uri.parse('https://verifier.divine.video/auth/twitter/start'), - authenticate: - ({ - required String url, - required String callbackUrlScheme, - required FlutterWebAuth2Options options, - }) async => throw PlatformException(code: 'BOOM'), + authenticate: ({ + required String url, + required String callbackUrlScheme, + required FlutterWebAuth2Options options, + }) async => throw PlatformException(code: 'BOOM'), ), throwsA(isA()), ); diff --git a/mobile/test/features/people_lists/bloc/people_lists_bloc_test.dart b/mobile/test/features/people_lists/bloc/people_lists_bloc_test.dart index 45da386fa0..9322ed4f0c 100644 --- a/mobile/test/features/people_lists/bloc/people_lists_bloc_test.dart +++ b/mobile/test/features/people_lists/bloc/people_lists_bloc_test.dart @@ -248,8 +248,7 @@ void main() { ), ).thenAnswer( (_) async => const PeopleListPublishResult.submitted( - eventId: - '1111111111111111111111111111111111111111111111111111111111111111', + eventId: '1111111111111111111111111111111111111111111111111111111111111111', ), ); }, @@ -298,8 +297,7 @@ void main() { ), ).thenAnswer( (_) async => const PeopleListPublishResult.submitted( - eventId: - '2222222222222222222222222222222222222222222222222222222222222222', + eventId: '2222222222222222222222222222222222222222222222222222222222222222', ), ); }, @@ -559,8 +557,7 @@ void main() { ), ).thenAnswer( (_) async => const PeopleListPublishResult.submitted( - eventId: - '3333333333333333333333333333333333333333333333333333333333333333', + eventId: '3333333333333333333333333333333333333333333333333333333333333333', ), ); }, @@ -614,8 +611,7 @@ void main() { () => repository.deleteList(ownerPubkey: _ownerA, listId: 'list-1'), ).thenAnswer( (_) async => const PeopleListPublishResult.submitted( - eventId: - '4444444444444444444444444444444444444444444444444444444444444444', + eventId: '4444444444444444444444444444444444444444444444444444444444444444', ), ); }, @@ -705,8 +701,7 @@ void main() { ), ).thenAnswer( (_) async => const PeopleListPublishResult.submitted( - eventId: - '5555555555555555555555555555555555555555555555555555555555555555', + eventId: '5555555555555555555555555555555555555555555555555555555555555555', ), ); }, @@ -823,8 +818,7 @@ void main() { ), ).thenAnswer( (_) async => const PeopleListPublishResult.submitted( - eventId: - '6666666666666666666666666666666666666666666666666666666666666666', + eventId: '6666666666666666666666666666666666666666666666666666666666666666', ), ); when( @@ -835,8 +829,7 @@ void main() { ), ).thenAnswer( (_) async => const PeopleListPublishResult.submitted( - eventId: - '7777777777777777777777777777777777777777777777777777777777777777', + eventId: '7777777777777777777777777777777777777777777777777777777777777777', ), ); }, @@ -911,8 +904,7 @@ void main() { ), ).thenAnswer( (_) async => const PeopleListPublishResult.submitted( - eventId: - '8888888888888888888888888888888888888888888888888888888888888888', + eventId: '8888888888888888888888888888888888888888888888888888888888888888', ), ); }, diff --git a/mobile/test/firebase_options_android_config_test.dart b/mobile/test/firebase_options_android_config_test.dart index 19e131b869..3b86d3817b 100644 --- a/mobile/test/firebase_options_android_config_test.dart +++ b/mobile/test/firebase_options_android_config_test.dart @@ -11,11 +11,9 @@ import 'package:openvine/firebase_options.dart'; void main() { group(DefaultFirebaseOptions, () { test('Android Firebase options match each registered app', () { - final config = - jsonDecode( - File('android/app/google-services.json').readAsStringSync(), - ) - as Map; + final config = jsonDecode( + File('android/app/google-services.json').readAsStringSync(), + ) as Map; final projectInfo = config['project_info'] as Map; final clients = config['client'] as List; diff --git a/mobile/test/goldens/widgets/goldens/divine_button_sizes.png b/mobile/test/goldens/widgets/goldens/divine_button_sizes.png index 38567ef8d9..fc56a522cc 100644 Binary files a/mobile/test/goldens/widgets/goldens/divine_button_sizes.png and b/mobile/test/goldens/widgets/goldens/divine_button_sizes.png differ diff --git a/mobile/test/goldens/widgets/goldens/divine_button_types.png b/mobile/test/goldens/widgets/goldens/divine_button_types.png index d1ec73e222..64a8384409 100644 Binary files a/mobile/test/goldens/widgets/goldens/divine_button_types.png and b/mobile/test/goldens/widgets/goldens/divine_button_types.png differ diff --git a/mobile/test/goldens/widgets/notification_rows_golden_test.dart b/mobile/test/goldens/widgets/notification_rows_golden_test.dart index fd54125058..5d0edabba7 100644 --- a/mobile/test/goldens/widgets/notification_rows_golden_test.dart +++ b/mobile/test/goldens/widgets/notification_rows_golden_test.dart @@ -155,8 +155,7 @@ Widget _scenarioColumn({required double textScaleFactor}) { timestamp: _notificationTimestamp, videoTitle: 'A vine that found a new shelf', listTitle: 'Literature', - listCoordinate: - '30005:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:literature', + listCoordinate: '30005:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:literature', ); return Column( diff --git a/mobile/test/in_memory/in_memory_auth_service.dart b/mobile/test/in_memory/in_memory_auth_service.dart index ae3fe7931d..d4f549d155 100644 --- a/mobile/test/in_memory/in_memory_auth_service.dart +++ b/mobile/test/in_memory/in_memory_auth_service.dart @@ -2,6 +2,7 @@ // ABOUTME: Provides authentication without persistent storage import 'dart:async'; + import '../builders/auth_state_builder.dart'; class InMemoryAuthService { diff --git a/mobile/test/infrastructure/mass_test_generation_test.dart b/mobile/test/infrastructure/mass_test_generation_test.dart index b699c2f973..177316d8c2 100644 --- a/mobile/test/infrastructure/mass_test_generation_test.dart +++ b/mobile/test/infrastructure/mass_test_generation_test.dart @@ -2,6 +2,7 @@ // ABOUTME: Ensures test generation scripts can create comprehensive test suites import 'dart:io'; + import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/mobile/test/infrastructure/test_infrastructure_setup_test.dart b/mobile/test/infrastructure/test_infrastructure_setup_test.dart index 097c4f42d1..e597d6af8b 100644 --- a/mobile/test/infrastructure/test_infrastructure_setup_test.dart +++ b/mobile/test/infrastructure/test_infrastructure_setup_test.dart @@ -2,6 +2,7 @@ // ABOUTME: Ensures coverage, analysis options, and test helpers are properly configured import 'dart:io'; + import 'package:flutter_test/flutter_test.dart'; import 'package:yaml/yaml.dart'; @@ -59,8 +60,7 @@ void main() { expect( content, contains('git rev-parse --git-common-dir'), - reason: - 'Hook installer should work from worktrees as well as the main checkout', + reason: 'Hook installer should work from worktrees as well as the main checkout', ); expect( content, diff --git a/mobile/test/mixins/codec_heavy_surface_guard_test.dart b/mobile/test/mixins/codec_heavy_surface_guard_test.dart index 7010bbf189..3bc406fcb6 100644 --- a/mobile/test/mixins/codec_heavy_surface_guard_test.dart +++ b/mobile/test/mixins/codec_heavy_surface_guard_test.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -62,10 +60,8 @@ void main() { 'completes', (tester) async { await pumpHost(tester); - unawaited( - navKey.currentState!.push( - MaterialPageRoute(builder: (_) => const _GuardedScreen()), - ), + navKey.currentState!.push( + MaterialPageRoute(builder: (_) => const _GuardedScreen()), ); // Mid-transition: the screen is animating in, the signal must stay off so @@ -83,11 +79,9 @@ void main() { 'disabled', (tester) async { await pumpHost(tester); - unawaited( - navKey.currentState!.push( - MaterialPageRoute( - builder: (_) => const _ImmediateGuardedScreen(), - ), + navKey.currentState!.push( + MaterialPageRoute( + builder: (_) => const _ImmediateGuardedScreen(), ), ); @@ -106,10 +100,8 @@ void main() { testWidgets('releases the signal on pop', (tester) async { await pumpHost(tester); - unawaited( - navKey.currentState!.push( - MaterialPageRoute(builder: (_) => const _GuardedScreen()), - ), + navKey.currentState!.push( + MaterialPageRoute(builder: (_) => const _GuardedScreen()), ); await tester.pumpAndSettle(); expect(cubit.state.isActive, isTrue); @@ -124,10 +116,8 @@ void main() { ) async { await pumpHost(tester); - unawaited( - navKey.currentState!.push( - MaterialPageRoute(builder: (_) => const _GuardedScreen()), - ), + navKey.currentState!.push( + MaterialPageRoute(builder: (_) => const _GuardedScreen()), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); diff --git a/mobile/test/models/divine_video_draft_audio_path_test.dart b/mobile/test/models/divine_video_draft_audio_path_test.dart index 5c85306047..6c57f55f03 100644 --- a/mobile/test/models/divine_video_draft_audio_path_test.dart +++ b/mobile/test/models/divine_video_draft_audio_path_test.dart @@ -191,8 +191,7 @@ void main() { final draft = _draft().copyWith( selectedSound: AudioEvent( id: 'sound-id-1234567890123456789012345678901234567890123456789012', - pubkey: - 'abc123def456789012345678901234567890123456789012345678901234abcd', + pubkey: 'abc123def456789012345678901234567890123456789012345678901234abcd', createdAt: 1700000000, url: 'https://blossom.example/audio.aac', ), diff --git a/mobile/test/models/invite_models_test.dart b/mobile/test/models/invite_models_test.dart index 077b1a1e7d..ab688358ce 100644 --- a/mobile/test/models/invite_models_test.dart +++ b/mobile/test/models/invite_models_test.dart @@ -70,8 +70,7 @@ void main() { 'code': 'HN4P-QR56', 'claimed': true, 'claimedAt': '2025-01-15T10:30:00Z', - 'claimedBy': - 'aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa1', + 'claimedBy': 'aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa111aaa1', }, ], }; diff --git a/mobile/test/models/pending_upload_proofmode_test.dart b/mobile/test/models/pending_upload_proofmode_test.dart index fd9a85024f..5d8b3f58f8 100644 --- a/mobile/test/models/pending_upload_proofmode_test.dart +++ b/mobile/test/models/pending_upload_proofmode_test.dart @@ -20,10 +20,8 @@ void main() { 'abc123def456789012345678901234567890123456789012345678901234', sensorDataCsv: 'timestamp,lat,lon\n2025-01-01T10:00:00,37.7749,-122.4194', - pgpSignature: - '-----BEGIN PGP SIGNATURE-----\ntest_signature_content\n-----END PGP SIGNATURE-----', - publicKey: - '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest_public_key\n-----END PGP PUBLIC KEY BLOCK-----', + pgpSignature: '-----BEGIN PGP SIGNATURE-----\ntest_signature_content\n-----END PGP SIGNATURE-----', + publicKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest_public_key\n-----END PGP PUBLIC KEY BLOCK-----', deviceAttestation: 'attestation_token_xyz', timestamp: '2025-01-01T10:00:06Z', ); diff --git a/mobile/test/models/proofmode_flow_test.dart b/mobile/test/models/proofmode_flow_test.dart index 3e8dd58871..de3b3a5a2d 100644 --- a/mobile/test/models/proofmode_flow_test.dart +++ b/mobile/test/models/proofmode_flow_test.dart @@ -19,8 +19,7 @@ void main() { sensorDataCsv: 'timestamp,lat,lon\n2025-01-01,40.7,-74.0', pgpSignature: '-----BEGIN PGP SIGNATURE-----\ntest\n-----END PGP SIGNATURE-----', - publicKey: - '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----', + publicKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----', deviceAttestation: 'attestation_token_12345', ); diff --git a/mobile/test/models/saved_sound_test.dart b/mobile/test/models/saved_sound_test.dart index 25cb8973db..a72c7ccd85 100644 --- a/mobile/test/models/saved_sound_test.dart +++ b/mobile/test/models/saved_sound_test.dart @@ -26,10 +26,8 @@ void main() { catalogTags: const ['field recording', 'birds'], waveformSamples: const [0.1, 0.5, 0.2], sourceContext: const SavedSoundSourceContext( - videoEventId: - 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', - creatorPubkey: - '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + videoEventId: 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', + creatorPubkey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', creatorName: 'Maya', title: 'Birds at sunrise', description: 'The loudest tree on the block.', diff --git a/mobile/test/models/vine_draft_collab_ib_test.dart b/mobile/test/models/vine_draft_collab_ib_test.dart index 6896cd25e8..bef9697312 100644 --- a/mobile/test/models/vine_draft_collab_ib_test.dart +++ b/mobile/test/models/vine_draft_collab_ib_test.dart @@ -53,8 +53,7 @@ void main() { test('accepts inspiredByVideo', () { const ib = InspiredByInfo( - addressableId: - '34236:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc:my-video', + addressableId: '34236:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc:my-video', relayUrl: 'wss://relay.divine.video', ); diff --git a/mobile/test/providers/clip_manager_provider_test.dart b/mobile/test/providers/clip_manager_provider_test.dart index 60eb281e24..9256370af4 100644 --- a/mobile/test/providers/clip_manager_provider_test.dart +++ b/mobile/test/providers/clip_manager_provider_test.dart @@ -113,16 +113,15 @@ void main() { ({required clip, required duration, required onComplete}) async { completeTrim = onComplete; }; - NativeProofModeService.proofFileOverride = - ( - _, { - required enableAdvancedCawgEmbedding, - creatorBindingAssertion, - cawgIdentityAssertion, - verifiedIdentityBundle, - clips, - editorStateHistory, - }) async => null; + NativeProofModeService.proofFileOverride = ( + _, { + required enableAdvancedCawgEmbedding, + creatorBindingAssertion, + cawgIdentityAssertion, + verifiedIdentityBundle, + clips, + editorStateHistory, + }) async => null; }); tearDown(() { diff --git a/mobile/test/providers/explore_feed_refresh_retention_test.dart b/mobile/test/providers/explore_feed_refresh_retention_test.dart index 652548954f..eac83afb34 100644 --- a/mobile/test/providers/explore_feed_refresh_retention_test.dart +++ b/mobile/test/providers/explore_feed_refresh_retention_test.dart @@ -275,8 +275,7 @@ void main() { expect( currentFeed?.videos.map((video) => video.id), ['popular-classic'], - reason: - 'Loaded variant must not update before matching feed data is published.', + reason: 'Loaded variant must not update before matching feed data is published.', ); }, ); @@ -302,8 +301,7 @@ void main() { expect( container.read(popularVideosLoadedVariantProvider), isNull, - reason: - 'The UI must not treat the old Native page as the selected Classic page.', + reason: 'The UI must not treat the old Native page as the selected Classic page.', ); classicCompleter.complete( diff --git a/mobile/test/providers/nostr_service_identity_source_test.dart b/mobile/test/providers/nostr_service_identity_source_test.dart index 0b6c6d4fc8..cf99d154ce 100644 --- a/mobile/test/providers/nostr_service_identity_source_test.dart +++ b/mobile/test/providers/nostr_service_identity_source_test.dart @@ -446,8 +446,7 @@ void main() { expect( factory.callCount, equals(2), - reason: - 'C must wait for the in-flight B transition instead of interleaving.', + reason: 'C must wait for the in-flight B transition instead of interleaving.', ); bInitialize.complete(); @@ -822,8 +821,7 @@ void main() { expect( container.read(nostrSessionProvider).phase, equals(NostrSessionPhase.nostrReady), - reason: - 'the successful restore init records _lastPubkey and marks ready.', + reason: 'the successful restore init records _lastPubkey and marks ready.', ); // RPC-upgrade nudge: same pubkey re-emitted after the background @@ -1099,8 +1097,7 @@ void main() { expect( container.read(nostrServiceProvider), same(initialClient), - reason: - 'A retry candidate should not rebuild consumers until it is ready.', + reason: 'A retry candidate should not rebuild consumers until it is ready.', ); secondFailure.completeError(StateError('second initialize failed')); diff --git a/mobile/test/providers/profile_fetching_tab_visibility_test.dart b/mobile/test/providers/profile_fetching_tab_visibility_test.dart index aaccd12f0d..2f5dfda96c 100644 --- a/mobile/test/providers/profile_fetching_tab_visibility_test.dart +++ b/mobile/test/providers/profile_fetching_tab_visibility_test.dart @@ -124,8 +124,7 @@ void main() { expect( isFeedActive || isExploreActive || isProfileActive, isFalse, - reason: - 'No video tabs should be active when on camera tab, preventing profile fetch', + reason: 'No video tabs should be active when on camera tab, preventing profile fetch', ); }, ); @@ -142,28 +141,30 @@ void main() { container.dispose(); }); - test('ExploreScreen.onScreenVisible should set tab visibility to index 2', () { - // Arrange - Start with a different tab - container.read(tabVisibilityProvider.notifier).setActiveTab(0); - expect(container.read(tabVisibilityProvider), equals(0)); + test( + 'ExploreScreen.onScreenVisible should set tab visibility to index 2', + () { + // Arrange - Start with a different tab + container.read(tabVisibilityProvider.notifier).setActiveTab(0); + expect(container.read(tabVisibilityProvider), equals(0)); - // Act - Simulate ExploreScreen.onScreenVisible() - // This is what our fix does: ref.read(tabVisibilityProvider.notifier).setActiveTab(2); - container.read(tabVisibilityProvider.notifier).setActiveTab(2); + // Act - Simulate ExploreScreen.onScreenVisible() + // This is what our fix does: ref.read(tabVisibilityProvider.notifier).setActiveTab(2); + container.read(tabVisibilityProvider.notifier).setActiveTab(2); - // Assert - expect( - container.read(tabVisibilityProvider), - equals(2), - reason: - 'Tab visibility should be set to 2 when ExploreScreen becomes visible', - ); - expect( - container.read(isExploreTabActiveProvider), - isTrue, - reason: 'Explore tab should be marked as active', - ); - }); + // Assert + expect( + container.read(tabVisibilityProvider), + equals(2), + reason: 'Tab visibility should be set to 2 when ExploreScreen becomes visible', + ); + expect( + container.read(isExploreTabActiveProvider), + isTrue, + reason: 'Explore tab should be marked as active', + ); + }, + ); test( 'Profile fetching should work immediately after ExploreScreen becomes visible', @@ -192,8 +193,7 @@ void main() { expect( canFetch, isTrue, - reason: - 'Should be able to fetch profiles after ExploreScreen becomes visible', + reason: 'Should be able to fetch profiles after ExploreScreen becomes visible', ); }, ); diff --git a/mobile/test/providers/push_notification_sync_test.dart b/mobile/test/providers/push_notification_sync_test.dart index 8ec4221789..3d32f56a8c 100644 --- a/mobile/test/providers/push_notification_sync_test.dart +++ b/mobile/test/providers/push_notification_sync_test.dart @@ -1510,9 +1510,9 @@ void main() { when( () => authService.currentIdentity, ).thenReturn(_identity(pubkeyA)); - final nostrSession = - container.read(nostrSessionProvider.notifier) - as _TestNostrSession; + final nostrSession = container.read( + nostrSessionProvider.notifier, + ) as _TestNostrSession; nostrSession.setReadiness( NostrSessionReadiness.nostrReady( pubkey: pubkeyA, @@ -1596,9 +1596,9 @@ void main() { when( () => authService.currentIdentity, ).thenReturn(_identity(pubkeyA)); - final nostrSession = - container.read(nostrSessionProvider.notifier) - as _TestNostrSession; + final nostrSession = container.read( + nostrSessionProvider.notifier, + ) as _TestNostrSession; nostrSession.setReadiness( NostrSessionReadiness.nostrReady( pubkey: pubkeyA, @@ -3009,9 +3009,9 @@ void main() { when( () => authService.currentIdentity, ).thenReturn(_identity(pubkeyA)); - final nostrSession = - container.read(nostrSessionProvider.notifier) - as _TestNostrSession; + final nostrSession = container.read( + nostrSessionProvider.notifier, + ) as _TestNostrSession; nostrSession.setReadiness( NostrSessionReadiness.nostrReady( pubkey: pubkeyA, diff --git a/mobile/test/providers/subtitle_providers_test.dart b/mobile/test/providers/subtitle_providers_test.dart index 0821778ab1..c504812456 100644 --- a/mobile/test/providers/subtitle_providers_test.dart +++ b/mobile/test/providers/subtitle_providers_test.dart @@ -453,26 +453,28 @@ void main() { expect(requestedDelays, hasLength(3)); }); - test('prefers embedded textTrackContent over Blossom sha256 fetch', () async { - final container = createContainer(); - addTearDown(container.dispose); + test( + 'prefers embedded textTrackContent over Blossom sha256 fetch', + () async { + final container = createContainer(); + addTearDown(container.dispose); - const embeddedVtt = - 'WEBVTT\n\n1\n00:00:00.500 --> 00:00:01.000\n' - 'Embedded\n'; + const embeddedVtt = + 'WEBVTT\n\n1\n00:00:00.500 --> 00:00:01.000\n' + 'Embedded\n'; - final cues = await container.read( - subtitleCuesProvider( - videoId: 'test-id', - textTrackContent: embeddedVtt, - sha256: - 'abc123def456abc123def456abc123def456abc123def456abc123def456abcd', - ).future, - ); + final cues = await container.read( + subtitleCuesProvider( + videoId: 'test-id', + textTrackContent: embeddedVtt, + sha256: 'abc123def456abc123def456abc123def456abc123def456abc123def456abcd', + ).future, + ); - expect(cues, hasLength(1)); - expect(cues[0].text, equals('Embedded')); - }); + expect(cues, hasLength(1)); + expect(cues[0].text, equals('Embedded')); + }, + ); test('returns empty list when sha256 is empty string', () async { final container = createContainer(); diff --git a/mobile/test/providers/video_editor_provider_test.dart b/mobile/test/providers/video_editor_provider_test.dart index afb71e58e4..501ffdd8d1 100644 --- a/mobile/test/providers/video_editor_provider_test.dart +++ b/mobile/test/providers/video_editor_provider_test.dart @@ -661,13 +661,12 @@ void main() { originalAspectRatio: 9 / 16, ); - VideoEditorRenderService.renderVideoToClipOverride = - ({ - required clips, - required editorStateHistory, - parameters, - taskId, - }) async => (renderedClip, null); + VideoEditorRenderService.renderVideoToClipOverride = ({ + required clips, + required editorStateHistory, + parameters, + taskId, + }) async => (renderedClip, null); await notifier.startRenderVideo(); @@ -729,13 +728,12 @@ void main() { duration: const Duration(seconds: 2), ); - VideoEditorRenderService.renderVideoToClipOverride = - ({ - required clips, - required editorStateHistory, - parameters, - taskId, - }) async => throw Exception('C2PA network failure'); // hung proof + VideoEditorRenderService.renderVideoToClipOverride = ({ + required clips, + required editorStateHistory, + parameters, + taskId, + }) async => throw Exception('C2PA network failure'); // hung proof await notifier.startRenderVideo(); @@ -757,13 +755,12 @@ void main() { targetAspectRatio: .vertical, originalAspectRatio: 9 / 16, ); - VideoEditorRenderService.renderVideoToClipOverride = - ({ - required clips, - required editorStateHistory, - parameters, - taskId, - }) async => (renderedClip, null); + VideoEditorRenderService.renderVideoToClipOverride = ({ + required clips, + required editorStateHistory, + parameters, + taskId, + }) async => (renderedClip, null); await notifier.startRenderVideo(); @@ -1011,13 +1008,12 @@ void main() { // Never completes and never throws — a hung native call. final hung = Completer<(DivineVideoClip, String?)>(); - VideoEditorRenderService.renderVideoToClipOverride = - ({ - required clips, - required editorStateHistory, - parameters, - taskId, - }) => hung.future; + VideoEditorRenderService.renderVideoToClipOverride = ({ + required clips, + required editorStateHistory, + parameters, + taskId, + }) => hung.future; unawaited(notifier.startRenderVideo()); async.flushMicrotasks(); @@ -1315,16 +1311,15 @@ void main() { ); // Network still down on retry — the re-sign throws. - NativeProofModeService.proofFileOverride = - ( - file, { - required enableAdvancedCawgEmbedding, - creatorBindingAssertion, - cawgIdentityAssertion, - verifiedIdentityBundle, - clips, - editorStateHistory, - }) async => throw Exception('still offline'); + NativeProofModeService.proofFileOverride = ( + file, { + required enableAdvancedCawgEmbedding, + creatorBindingAssertion, + cawgIdentityAssertion, + verifiedIdentityBundle, + clips, + editorStateHistory, + }) async => throw Exception('still offline'); await notifier.retryC2paSigning(); @@ -1561,13 +1556,12 @@ void main() { ); void failRenderWith(VideoRenderFailedException failure) { - VideoEditorRenderService.renderVideoToClipOverride = - ({ - required clips, - required editorStateHistory, - parameters, - taskId, - }) async => throw failure; + VideoEditorRenderService.renderVideoToClipOverride = ({ + required clips, + required editorStateHistory, + parameters, + taskId, + }) async => throw failure; } test('tags outcome=failed with the reason behind a render that produced ' @@ -1633,13 +1627,12 @@ void main() { test('tags outcome=error with the type when the render throws', () async { final notifier = container.read(videoEditorProvider.notifier); addOneClip(); - VideoEditorRenderService.renderVideoToClipOverride = - ({ - required clips, - required editorStateHistory, - parameters, - taskId, - }) async => throw StateError('render boom'); + VideoEditorRenderService.renderVideoToClipOverride = ({ + required clips, + required editorStateHistory, + parameters, + taskId, + }) async => throw StateError('render boom'); await notifier.startRenderVideo(); final trace = performanceMonitor.traces.single; @@ -1738,13 +1731,12 @@ void main() { ); final renderCompleter = Completer<(DivineVideoClip, String?)>(); - VideoEditorRenderService.renderVideoToClipOverride = - ({ - required clips, - required editorStateHistory, - parameters, - taskId, - }) => renderCompleter.future; + VideoEditorRenderService.renderVideoToClipOverride = ({ + required clips, + required editorStateHistory, + parameters, + taskId, + }) => renderCompleter.future; final render = notifier.startRenderVideo(); @@ -3756,16 +3748,15 @@ void main() { NativeProofModeService.proofFileOverride; restoreProofFileOverride = () => NativeProofModeService.proofFileOverride = originalProofFileOverride; - NativeProofModeService.proofFileOverride = - ( - file, { - required enableAdvancedCawgEmbedding, - creatorBindingAssertion, - cawgIdentityAssertion, - verifiedIdentityBundle, - clips, - editorStateHistory, - }) async => null; + NativeProofModeService.proofFileOverride = ( + file, { + required enableAdvancedCawgEmbedding, + creatorBindingAssertion, + cawgIdentityAssertion, + verifiedIdentityBundle, + clips, + editorStateHistory, + }) async => null; when( () => mockDraftStorage.draftExists(any()), ).thenAnswer((_) async => false); diff --git a/mobile/test/providers/video_publish_provider_test.dart b/mobile/test/providers/video_publish_provider_test.dart index 8773f3e994..90aa478184 100644 --- a/mobile/test/providers/video_publish_provider_test.dart +++ b/mobile/test/providers/video_publish_provider_test.dart @@ -644,13 +644,12 @@ void main() { ) async { final container = await pumpHarness(tester); final hung = Completer<(DivineVideoClip, String?)>(); - VideoEditorRenderService.renderVideoToClipOverride = - ({ - required clips, - required editorStateHistory, - parameters, - taskId, - }) => hung.future; + VideoEditorRenderService.renderVideoToClipOverride = ({ + required clips, + required editorStateHistory, + parameters, + taskId, + }) => hung.future; VideoEditorRenderService.crashReporterOverride = (_, _) {}; final context = tester.element(find.byType(SizedBox)); diff --git a/mobile/test/providers/video_recorder_audio_service_factory_test.dart b/mobile/test/providers/video_recorder_audio_service_factory_test.dart index 7bc08d48bf..d2753bea1f 100644 --- a/mobile/test/providers/video_recorder_audio_service_factory_test.dart +++ b/mobile/test/providers/video_recorder_audio_service_factory_test.dart @@ -62,8 +62,7 @@ void main() { final selectedSound = AudioEvent( id: 'sound_123', - pubkey: - 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', createdAt: 1700000000, title: 'Test Sound', duration: 6.0, diff --git a/mobile/test/repositories/account_deletion_recovery_repository_test.dart b/mobile/test/repositories/account_deletion_recovery_repository_test.dart index 8b61c01fe6..38a52accf0 100644 --- a/mobile/test/repositories/account_deletion_recovery_repository_test.dart +++ b/mobile/test/repositories/account_deletion_recovery_repository_test.dart @@ -83,8 +83,7 @@ void main() { }), ).fetchStatus( attemptId: 'attempt-1', - pubkeyHex: - '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', + pubkeyHex: '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', ); expect(result.status, AccountDeletionAttemptStatus.completed); @@ -118,8 +117,7 @@ void main() { ), ).fetchStatus( attemptId: 'attempt-1', - pubkeyHex: - '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', + pubkeyHex: '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', ), throwsA( isA() @@ -142,8 +140,7 @@ void main() { delay: (_) async {}, ).fetchStatus( attemptId: 'attempt-1', - pubkeyHex: - '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', + pubkeyHex: '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', ), throwsA( isA() diff --git a/mobile/test/repositories/avatar_svg_repository_test.dart b/mobile/test/repositories/avatar_svg_repository_test.dart index 2f1a8d8588..fdbda25117 100644 --- a/mobile/test/repositories/avatar_svg_repository_test.dart +++ b/mobile/test/repositories/avatar_svg_repository_test.dart @@ -136,20 +136,23 @@ void main() { expect(repository.load(url), completion(isNull)); }); - test('rejects an end element after the root with a shape left open', () { - // A `rect` is a shape, not a group, so flutter_svg never pushes it onto - // `_parentDrawables`. Any guard that mirrors that stack instead of the - // document desynchronises here and lets the payload through. - final repository = repositoryFor( - http.Response( - '$svgOpenTag', - 200, - headers: {'content-type': 'image/svg+xml'}, - ), - ); + test( + 'rejects an end element after the root with a shape left open', + () { + // A `rect` is a shape, not a group, so flutter_svg never pushes it onto + // `_parentDrawables`. Any guard that mirrors that stack instead of the + // document desynchronises here and lets the payload through. + final repository = repositoryFor( + http.Response( + '$svgOpenTag', + 200, + headers: {'content-type': 'image/svg+xml'}, + ), + ); - expect(repository.load(url), completion(isNull)); - }); + expect(repository.load(url), completion(isNull)); + }, + ); test('accepts a nested svg element', () async { // `SvgParser` supports a nested `` and pushes its own group for it, diff --git a/mobile/test/repositories/feed_repository_impl_test.dart b/mobile/test/repositories/feed_repository_impl_test.dart index 4f8091e40c..ecc0e24e41 100644 --- a/mobile/test/repositories/feed_repository_impl_test.dart +++ b/mobile/test/repositories/feed_repository_impl_test.dart @@ -95,9 +95,9 @@ void main() { ); repository = container.read(feedRepositoryProvider); - final feed = - container.read(newVideosFeedProvider.notifier) - as _TestNewVideosFeed; + final feed = container.read( + newVideosFeedProvider.notifier, + ) as _TestNewVideosFeed; await container.read(newVideosFeedProvider.future); feed.emit([_video('1')], hasMore: true); @@ -137,9 +137,9 @@ void main() { ); repository = container.read(feedRepositoryProvider); - final feed = - container.read(newVideosFeedProvider.notifier) - as _TestNewVideosFeed; + final feed = container.read( + newVideosFeedProvider.notifier, + ) as _TestNewVideosFeed; await container.read(newVideosFeedProvider.future); feed.emit([_video('1')], hasMore: true); diff --git a/mobile/test/router/aasa_claim_coverage_test.dart b/mobile/test/router/aasa_claim_coverage_test.dart index 25b0e11279..c5bbbc7c7a 100644 --- a/mobile/test/router/aasa_claim_coverage_test.dart +++ b/mobile/test/router/aasa_claim_coverage_test.dart @@ -47,18 +47,12 @@ const _expectedClaimPatternsByHost = >{ }; const _allowlistedClaims = { - 'divine.video /app/callback': - 'Keycast OAuth redirect consumed by the OAuth client, not DeepLinkService.', - 'www.divine.video /app/callback': - 'Keycast OAuth redirect consumed by the OAuth client, not DeepLinkService.', - 'login.divine.video /app/callback': - 'Keycast OAuth redirect consumed by the OAuth client, not DeepLinkService.', - 'login.divine.video /app/callback/*': - 'Keycast OAuth redirect with path params, consumed before app deep-link routing.', - 'login.divine.video /verify-email': - 'Auth link path matches an internal GoRoute directly; no DeepLinkType is emitted.', - 'login.divine.video /reset-password': - 'Auth link path matches an internal GoRoute redirect directly; no DeepLinkType is emitted.', + 'divine.video /app/callback': 'Keycast OAuth redirect consumed by the OAuth client, not DeepLinkService.', + 'www.divine.video /app/callback': 'Keycast OAuth redirect consumed by the OAuth client, not DeepLinkService.', + 'login.divine.video /app/callback': 'Keycast OAuth redirect consumed by the OAuth client, not DeepLinkService.', + 'login.divine.video /app/callback/*': 'Keycast OAuth redirect with path params, consumed before app deep-link routing.', + 'login.divine.video /verify-email': 'Auth link path matches an internal GoRoute directly; no DeepLinkType is emitted.', + 'login.divine.video /reset-password': 'Auth link path matches an internal GoRoute redirect directly; no DeepLinkType is emitted.', }; // divine.video AASA claims the `divine://` scheme deliberately does NOT diff --git a/mobile/test/router/account_deletion_processing_gate_test.dart b/mobile/test/router/account_deletion_processing_gate_test.dart index 2e137e689a..5583018c63 100644 --- a/mobile/test/router/account_deletion_processing_gate_test.dart +++ b/mobile/test/router/account_deletion_processing_gate_test.dart @@ -152,8 +152,7 @@ void main() { .record( pubkeyHex: _pubkey, attempt: _processing, - vanishEventId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + vanishEventId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', ); final router = GoRouter( initialLocation: AccountDeletionRecoveryScreen.path, @@ -204,8 +203,7 @@ void main() { .record( pubkeyHex: _pubkey, attempt: _recoverable, - vanishEventId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + vanishEventId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', submissionOwnedLocally: true, ); container.invalidate(currentAccountDeletionAttemptProvider); @@ -358,8 +356,7 @@ void main() { .record( pubkeyHex: _pubkey, attempt: _processing, - vanishEventId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + vanishEventId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', ); container.invalidate(currentAccountDeletionAttemptProvider); await tester.runAsync( diff --git a/mobile/test/router/app_shell_obscured_test.dart b/mobile/test/router/app_shell_obscured_test.dart index b7f6fd1e4b..bf4eaccd4a 100644 --- a/mobile/test/router/app_shell_obscured_test.dart +++ b/mobile/test/router/app_shell_obscured_test.dart @@ -140,20 +140,16 @@ void main() { expect(container.read(shellObscuredProvider), isFalse); // Push a profile over the shell → obscured. - unawaited( - navigator.push( - MaterialPageRoute(builder: (_) => const SizedBox.shrink()), - ), + navigator.push( + MaterialPageRoute(builder: (_) => const SizedBox.shrink()), ); await tester.pumpAndSettle(); expect(container.read(shellObscuredProvider), isTrue); // Push a fullscreen video over the profile → still obscured (the shell's // RouteAware does not fire for a push above the profile). - unawaited( - navigator.push( - MaterialPageRoute(builder: (_) => const SizedBox.shrink()), - ), + navigator.push( + MaterialPageRoute(builder: (_) => const SizedBox.shrink()), ); await tester.pumpAndSettle(); expect(container.read(shellObscuredProvider), isTrue); @@ -288,11 +284,9 @@ void main() { // the background upload-failure sheet, which needs no user tap. It takes // no overlay token of its own, so it must not disturb the sheet's. final shellContext = tester.element(find.byType(AppShell)); - unawaited( - Navigator.of(shellContext, rootNavigator: true).push( - MaterialPageRoute( - builder: (_) => const Scaffold(body: Text('Unrelated modal')), - ), + Navigator.of(shellContext, rootNavigator: true).push( + MaterialPageRoute( + builder: (_) => const Scaffold(body: Text('Unrelated modal')), ), ); await tester.pumpAndSettle(); diff --git a/mobile/test/router/branch_scoped_context_test.dart b/mobile/test/router/branch_scoped_context_test.dart index 236ddae30d..f9a52b3d2c 100644 --- a/mobile/test/router/branch_scoped_context_test.dart +++ b/mobile/test/router/branch_scoped_context_test.dart @@ -265,8 +265,7 @@ void main() { ) async { RouteContext? captured; final router = GoRouter( - initialLocation: - '/profile/npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqlz5yt?utm_source=test', + initialLocation: '/profile/npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqlz5yt?utm_source=test', routes: [ StatefulShellRoute( builder: (context, state, shell) => shell, diff --git a/mobile/test/router/category_route_test.dart b/mobile/test/router/category_route_test.dart index 4d6f5163c9..c599b99ccb 100644 --- a/mobile/test/router/category_route_test.dart +++ b/mobile/test/router/category_route_test.dart @@ -114,16 +114,14 @@ void main() { late CategoryGalleryScreen screen; await _buildWithContext(tester, (context) { - screen = - route.builder!( - context, - _FakeGoRouterState( - location: '/categories/animals', - pathParameters: const {'categoryName': 'animals'}, - extra: const VideoCategory(name: 'animals', videoCount: 1), - ), - ) - as CategoryGalleryScreen; + screen = route.builder!( + context, + _FakeGoRouterState( + location: '/categories/animals', + pathParameters: const {'categoryName': 'animals'}, + extra: const VideoCategory(name: 'animals', videoCount: 1), + ), + ) as CategoryGalleryScreen; }); expect(screen.category.name, 'animals'); diff --git a/mobile/test/router/explore_tab_tap_navigation_test.dart b/mobile/test/router/explore_tab_tap_navigation_test.dart index 1c178596fa..28ecf74a56 100644 --- a/mobile/test/router/explore_tab_tap_navigation_test.dart +++ b/mobile/test/router/explore_tab_tap_navigation_test.dart @@ -64,8 +64,7 @@ void main() { expect( exploreLocation, ExploreScreen.path, - reason: - 'Tapping explore tab should navigate to grid mode (/explore), not feed mode (/explore/0)', + reason: 'Tapping explore tab should navigate to grid mode (/explore), not feed mode (/explore/0)', ); }, // TODO(any): Fix and re-enable these tests diff --git a/mobile/test/router/login_flow_redirect_test.dart b/mobile/test/router/login_flow_redirect_test.dart index b2845f6ce4..0b4aab507f 100644 --- a/mobile/test/router/login_flow_redirect_test.dart +++ b/mobile/test/router/login_flow_redirect_test.dart @@ -282,8 +282,7 @@ void main() { expect( redirect, isNull, - reason: - 'Authenticated users should remain on reset-password deep links', + reason: 'Authenticated users should remain on reset-password deep links', ); }, ); diff --git a/mobile/test/router/routes/restored_route_extras_test.dart b/mobile/test/router/routes/restored_route_extras_test.dart index 46b774204a..8084573ddc 100644 --- a/mobile/test/router/routes/restored_route_extras_test.dart +++ b/mobile/test/router/routes/restored_route_extras_test.dart @@ -50,19 +50,17 @@ void main() { late OtherProfileScreenRouter screen; await _buildWithContext(tester, (context) { - screen = - route.builder!( - context, - _FakeGoRouterState( - location: '/profile/npub1test', - pathParameters: const {'npub': 'npub1test'}, - extra: const { - 'displayName': 'Ada', - 'avatarUrl': 42, - }, - ), - ) - as OtherProfileScreenRouter; + screen = route.builder!( + context, + _FakeGoRouterState( + location: '/profile/npub1test', + pathParameters: const {'npub': 'npub1test'}, + extra: const { + 'displayName': 'Ada', + 'avatarUrl': 42, + }, + ), + ) as OtherProfileScreenRouter; }); expect(screen.npub, 'npub1test'); @@ -80,16 +78,14 @@ void main() { late SoundDetailScreen screen; await _buildWithContext(tester, (context) { - screen = - route.builder!( - context, - _FakeGoRouterState( - location: '/sound/sound-1', - pathParameters: const {'id': 'sound-1'}, - extra: {'sound': sound}, - ), - ) - as SoundDetailScreen; + screen = route.builder!( + context, + _FakeGoRouterState( + location: '/sound/sound-1', + pathParameters: const {'id': 'sound-1'}, + extra: {'sound': sound}, + ), + ) as SoundDetailScreen; }); expect(screen.sound, same(sound)); @@ -138,48 +134,40 @@ void main() { late VideoEditorScreen draft; await _buildWithContext(tester, (context) { - fromLibrary = - editorRoute.builder!( - context, - _FakeGoRouterState( - location: VideoEditorScreen.pathFor(fromLibrary: true), - pathParameters: const {}, - ), - ) - as VideoEditorScreen; - fromRecorder = - editorRoute.builder!( - context, - _FakeGoRouterState( - location: VideoEditorScreen.pathFor(), - pathParameters: const {}, - ), - ) - as VideoEditorScreen; + fromLibrary = editorRoute.builder!( + context, + _FakeGoRouterState( + location: VideoEditorScreen.pathFor(fromLibrary: true), + pathParameters: const {}, + ), + ) as VideoEditorScreen; + fromRecorder = editorRoute.builder!( + context, + _FakeGoRouterState( + location: VideoEditorScreen.pathFor(), + pathParameters: const {}, + ), + ) as VideoEditorScreen; // The map extra this route used to read is no longer consulted, so a // restored session cannot resurrect a half-migrated origin (#3335). - legacyExtra = - editorRoute.builder!( - context, - _FakeGoRouterState( - location: VideoEditorScreen.path, - pathParameters: const {}, - extra: const {'fromLibrary': true}, - ), - ) - as VideoEditorScreen; - draft = - draftRoute.builder!( - context, - _FakeGoRouterState( - location: VideoEditorScreen.pathFor( - draftId: 'draft-1', - fromLibrary: true, - ), - pathParameters: const {'draftId': 'draft-1'}, - ), - ) - as VideoEditorScreen; + legacyExtra = editorRoute.builder!( + context, + _FakeGoRouterState( + location: VideoEditorScreen.path, + pathParameters: const {}, + extra: const {'fromLibrary': true}, + ), + ) as VideoEditorScreen; + draft = draftRoute.builder!( + context, + _FakeGoRouterState( + location: VideoEditorScreen.pathFor( + draftId: 'draft-1', + fromLibrary: true, + ), + pathParameters: const {'draftId': 'draft-1'}, + ), + ) as VideoEditorScreen; }); expect(fromLibrary.fromLibrary, isTrue); @@ -216,37 +204,31 @@ void main() { late VideoMetadataScreen unsupported; await _buildWithContext(tester, (context) { - capture = - route.builder!( - context, - _FakeGoRouterState( - location: VideoMetadataScreen.pathForDraft( - isStopMotion: false, - ), - pathParameters: const {}, - ), - ) - as VideoMetadataScreen; - stopMotion = - route.builder!( - context, - _FakeGoRouterState( - location: VideoMetadataScreen.pathForDraft( - isStopMotion: true, - ), - pathParameters: const {}, - ), - ) - as VideoMetadataScreen; - unsupported = - route.builder!( - context, - _FakeGoRouterState( - location: '${VideoMetadataScreen.path}?mode=upload', - pathParameters: const {}, - ), - ) - as VideoMetadataScreen; + capture = route.builder!( + context, + _FakeGoRouterState( + location: VideoMetadataScreen.pathForDraft( + isStopMotion: false, + ), + pathParameters: const {}, + ), + ) as VideoMetadataScreen; + stopMotion = route.builder!( + context, + _FakeGoRouterState( + location: VideoMetadataScreen.pathForDraft( + isStopMotion: true, + ), + pathParameters: const {}, + ), + ) as VideoMetadataScreen; + unsupported = route.builder!( + context, + _FakeGoRouterState( + location: '${VideoMetadataScreen.path}?mode=upload', + pathParameters: const {}, + ), + ) as VideoMetadataScreen; }); expect(capture.draftMode, VideoRecorderMode.capture); @@ -269,44 +251,36 @@ void main() { late SubtitleEditorScreen subtitleNoExtra; await _buildWithContext(tester, (context) { - metadata = - metadataRoute.builder!( - context, - _FakeGoRouterState( - location: '/video-edit/video-1', - pathParameters: const {'videoId': 'video-1'}, - extra: const {'id': 'video-1'}, - ), - ) - as VideoMetadataEditScreen; - subtitle = - subtitleRoute.builder!( - context, - _FakeGoRouterState( - location: '/subtitle-edit/video-1', - pathParameters: const {'videoId': 'video-1'}, - extra: const {'id': 'video-1'}, - ), - ) - as SubtitleEditorScreen; - metadataNoExtra = - metadataRoute.builder!( - context, - _FakeGoRouterState( - location: '/video-edit/video-1', - pathParameters: const {'videoId': 'video-1'}, - ), - ) - as VideoMetadataEditScreen; - subtitleNoExtra = - subtitleRoute.builder!( - context, - _FakeGoRouterState( - location: '/subtitle-edit/video-1', - pathParameters: const {'videoId': 'video-1'}, - ), - ) - as SubtitleEditorScreen; + metadata = metadataRoute.builder!( + context, + _FakeGoRouterState( + location: '/video-edit/video-1', + pathParameters: const {'videoId': 'video-1'}, + extra: const {'id': 'video-1'}, + ), + ) as VideoMetadataEditScreen; + subtitle = subtitleRoute.builder!( + context, + _FakeGoRouterState( + location: '/subtitle-edit/video-1', + pathParameters: const {'videoId': 'video-1'}, + extra: const {'id': 'video-1'}, + ), + ) as SubtitleEditorScreen; + metadataNoExtra = metadataRoute.builder!( + context, + _FakeGoRouterState( + location: '/video-edit/video-1', + pathParameters: const {'videoId': 'video-1'}, + ), + ) as VideoMetadataEditScreen; + subtitleNoExtra = subtitleRoute.builder!( + context, + _FakeGoRouterState( + location: '/subtitle-edit/video-1', + pathParameters: const {'videoId': 'video-1'}, + ), + ) as SubtitleEditorScreen; }); expect(metadata.videoId, 'video-1'); diff --git a/mobile/test/screens/comments/comment_item_test.dart b/mobile/test/screens/comments/comment_item_test.dart index d0df832a20..03d29281ab 100644 --- a/mobile/test/screens/comments/comment_item_test.dart +++ b/mobile/test/screens/comments/comment_item_test.dart @@ -272,10 +272,8 @@ void main() { .withContent('Ferns') .build() .copyWith( - videoUrl: - 'https://media.divine.video/6ab6f26428369761ff7fda84166f5dc4981d93d43370c30171d74b036286b020', - thumbnailUrl: - 'https://media.divine.video/748855341e45388bc6a2aeacccc68161b8da4a817b11b1e7527423582ec6d42b', + videoUrl: 'https://media.divine.video/6ab6f26428369761ff7fda84166f5dc4981d93d43370c30171d74b036286b020', + thumbnailUrl: 'https://media.divine.video/748855341e45388bc6a2aeacccc68161b8da4a817b11b1e7527423582ec6d42b', videoDimensions: '1080x1920', videoDuration: 6, videoBlurhash: diff --git a/mobile/test/screens/curated_list_feed_screen_test.dart b/mobile/test/screens/curated_list_feed_screen_test.dart index d8c6a1dbbc..52b228075e 100644 --- a/mobile/test/screens/curated_list_feed_screen_test.dart +++ b/mobile/test/screens/curated_list_feed_screen_test.dart @@ -935,11 +935,9 @@ void main() { // would keep publishing after close() with their completion dropped. await tester.tap(find.byTooltip('Back')); await tester.pump(); - final popScope = - tester.widget( - find.byWidgetPredicate((widget) => widget is PopScope).first, - ) - as PopScope; + final popScope = tester.widget( + find.byWidgetPredicate((widget) => widget is PopScope).first, + ) as PopScope; popScope.onPopInvokedWithResult!(false, null); await tester.pump(); expect(find.text('Owned List'), findsOneWidget); diff --git a/mobile/test/screens/feed/feed_mode_switch_test.dart b/mobile/test/screens/feed/feed_mode_switch_test.dart index e22e829082..3a312799a1 100644 --- a/mobile/test/screens/feed/feed_mode_switch_test.dart +++ b/mobile/test/screens/feed/feed_mode_switch_test.dart @@ -94,8 +94,7 @@ void main() { VideoEvent video({required String id}) { return VideoEvent( id: id, - pubkey: - 'd4e5f6789012345678901234567890abcdef123456789012345678901234a1b2c3', + pubkey: 'd4e5f6789012345678901234567890abcdef123456789012345678901234a1b2c3', createdAt: 1700000000, content: 'Test video', timestamp: DateTime.fromMillisecondsSinceEpoch(1700000000 * 1000), diff --git a/mobile/test/screens/feed/video_feed_page_web_test.dart b/mobile/test/screens/feed/video_feed_page_web_test.dart index 1e54efe4bf..645d592742 100644 --- a/mobile/test/screens/feed/video_feed_page_web_test.dart +++ b/mobile/test/screens/feed/video_feed_page_web_test.dart @@ -33,8 +33,7 @@ void main() { testWidgets('renders Auto action in the home web overlay', (tester) async { final video = createTestVideoEvent( id: 'a1b2c3d4e5f6789012345678901234567890abcdef123456789012345678901234', - pubkey: - 'd4e5f6789012345678901234567890abcdef123456789012345678901234a1b2c3', + pubkey: 'd4e5f6789012345678901234567890abcdef123456789012345678901234a1b2c3', videoUrl: 'https://example.com/video1.mp4', ); final state = VideoFeedBlocState( diff --git a/mobile/test/screens/inbox/conversation/conversation_view_resolvers_test.dart b/mobile/test/screens/inbox/conversation/conversation_view_resolvers_test.dart index 5eed2d9ab7..e6cbfb4895 100644 --- a/mobile/test/screens/inbox/conversation/conversation_view_resolvers_test.dart +++ b/mobile/test/screens/inbox/conversation/conversation_view_resolvers_test.dart @@ -12,8 +12,7 @@ void main() { '1122334411223344112233441122334411223344112233441122334411223344'; const sharedVideoRef = DmSharedVideoRef( - coordinateOrId: - '34236:1122334411223344112233441122334411223344112233441122334411223344:skate-loop', + coordinateOrId: '34236:1122334411223344112233441122334411223344112233441122334411223344:skate-loop', videoKind: DmSharedVideoKind.addressableShortVideo, ); diff --git a/mobile/test/screens/inbox/conversation/conversation_view_test.dart b/mobile/test/screens/inbox/conversation/conversation_view_test.dart index 97b5917289..7b80687d21 100644 --- a/mobile/test/screens/inbox/conversation/conversation_view_test.dart +++ b/mobile/test/screens/inbox/conversation/conversation_view_test.dart @@ -85,8 +85,7 @@ void main() { const fallbackInvite = CollaboratorInvite( messageId: '9999999999999999999999999999999999999999999999999999999999999999', - videoAddress: - '34236:1122334411223344112233441122334411223344112233441122334411223344:skate-loop', + videoAddress: '34236:1122334411223344112233441122334411223344112233441122334411223344:skate-loop', videoKind: 34236, creatorPubkey: otherPubkey, videoDTag: 'skate-loop', @@ -414,8 +413,7 @@ void main() { ) async { final failedRow = OutgoingDm( id: 'rumor-failed-id', - conversationId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + conversationId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', recipientPubkey: otherPubkey, content: 'React to me', createdAt: now.millisecondsSinceEpoch ~/ 1000, @@ -447,13 +445,11 @@ void main() { final retired = kLegacyModerationPubkeys.first; final message = DmMessage( id: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', - conversationId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + conversationId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', senderPubkey: retired, content: 'React to me', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', ); await tester.pumpWidget( @@ -477,13 +473,11 @@ void main() { final retired = kLegacyModerationPubkeys.first; final message = DmMessage( id: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', - conversationId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + conversationId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', senderPubkey: retired, content: 'React to me', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', ); await tester.pumpWidget( @@ -1687,13 +1681,11 @@ void main() { ) async { final message = DmMessage( id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: otherPubkey, content: 'Hello there!', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', ); await tester.pumpWidget( @@ -1713,13 +1705,11 @@ void main() { testWidgets('maps message keys to their new indices', (tester) async { DmMessage message(String id, String content) => DmMessage( id: id, - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: otherPubkey, content: content, createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', ); final older = message( '1111111111111111111111111111111111111111111111111111111111111111', @@ -1753,53 +1743,53 @@ void main() { ); }); - testWidgets('keeps an expanded message open when a newer message arrives', ( - tester, - ) async { - final states = StreamController(); - addTearDown(states.close); - final olderMessage = DmMessage( - id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - senderPubkey: otherPubkey, - content: 'a' * (dmInitialDisplayCodeUnits + 1), - createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', - ); - final initialState = ConversationState( - status: ConversationStatus.loaded, - messages: [olderMessage], - ); + testWidgets( + 'keeps an expanded message open when a newer message arrives', + ( + tester, + ) async { + final states = StreamController(); + addTearDown(states.close); + final olderMessage = DmMessage( + id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + senderPubkey: otherPubkey, + content: 'a' * (dmInitialDisplayCodeUnits + 1), + createdAt: now.millisecondsSinceEpoch ~/ 1000, + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + ); + final initialState = ConversationState( + status: ConversationStatus.loaded, + messages: [olderMessage], + ); - await tester.pumpWidget( - buildSubject(state: initialState, stateStream: states.stream), - ); - await tester.tap(find.text(l10n.profileShowMore)); - await tester.pump(); - expect(find.text(l10n.profileShowLess), findsOneWidget); + await tester.pumpWidget( + buildSubject(state: initialState, stateStream: states.stream), + ); + await tester.tap(find.text(l10n.profileShowMore)); + await tester.pump(); + expect(find.text(l10n.profileShowLess), findsOneWidget); - final newerMessage = DmMessage( - id: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', - conversationId: olderMessage.conversationId, - senderPubkey: currentPubkey, - content: 'new reply', - createdAt: olderMessage.createdAt + 1, - giftWrapId: - 'bbbbbbbbccccccccddddddddeeeeeeeebbbbbbbbccccccccddddddddeeeeeeee', - ); - states.add( - ConversationState( - status: ConversationStatus.loaded, - messages: [newerMessage, olderMessage], - ), - ); - await tester.pumpAndSettle(); + final newerMessage = DmMessage( + id: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + conversationId: olderMessage.conversationId, + senderPubkey: currentPubkey, + content: 'new reply', + createdAt: olderMessage.createdAt + 1, + giftWrapId: 'bbbbbbbbccccccccddddddddeeeeeeeebbbbbbbbccccccccddddddddeeeeeeee', + ); + states.add( + ConversationState( + status: ConversationStatus.loaded, + messages: [newerMessage, olderMessage], + ), + ); + await tester.pumpAndSettle(); - expect(find.text(l10n.profileShowLess), findsOneWidget); - expect(find.byKey(ValueKey(newerMessage.id)), findsOneWidget); - }); + expect(find.text(l10n.profileShowLess), findsOneWidget); + expect(find.byKey(ValueKey(newerMessage.id)), findsOneWidget); + }, + ); // The whole retry affordance lived only in the view: the bloc test // covers the event's effects and the bubble test covers the icon, but @@ -1810,13 +1800,11 @@ void main() { ) async { final message = DmMessage( id: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: currentPubkey, content: 'Still recognizable', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', retractionStatus: DmRetractionStatus.failed, ); @@ -1852,13 +1840,11 @@ void main() { ) async { final message = DmMessage( id: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: currentPubkey, content: 'Still deleting', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', retractionStatus: DmRetractionStatus.pending, ); @@ -1905,8 +1891,7 @@ void main() { (tester) async { final pendingRow = OutgoingDm( id: 'rumor-test-id', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', recipientPubkey: otherPubkey, content: 'Optimistic in flight', createdAt: now.millisecondsSinceEpoch ~/ 1000, @@ -1945,8 +1930,7 @@ void main() { name: 'alice', rawData: const {}, createdAt: now, - eventId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + eventId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', ); await tester.pumpWidget(buildSubject(otherProfile: profile)); @@ -1966,8 +1950,7 @@ void main() { name: 'Jack', rawData: const {}, createdAt: now, - eventId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + eventId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', ); await tester.pumpWidget( @@ -2006,8 +1989,7 @@ void main() { name: 'Jack', rawData: const {'follower_count': 2100, 'vine_followers': 430}, createdAt: now, - eventId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + eventId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', ); await tester.pumpWidget(buildSubject(otherProfile: profile)); @@ -2027,8 +2009,7 @@ void main() { name: 'Jack', rawData: const {}, createdAt: now, - eventId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + eventId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', ); await tester.pumpWidget( @@ -2053,13 +2034,11 @@ void main() { const thumbnailUrl = 'https://cdn.divine.video/thumbs/skate-loop.jpg'; final message = DmMessage( id: '9999999999999999999999999999999999999999999999999999999999999999', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: otherPubkey, content: 'You were invited to collaborate.', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', tags: const [ ['divine', 'collab-invite'], [ @@ -2129,13 +2108,11 @@ void main() { (tester) async { final message = DmMessage( id: '9999999999999999999999999999999999999999999999999999999999999999', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: otherPubkey, content: 'You were invited to collaborate.', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', tags: const [ ['divine', 'collab-invite'], [ @@ -2185,13 +2162,11 @@ void main() { (tester) async { final message = DmMessage( id: '9999999999999999999999999999999999999999999999999999999999999999', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: otherPubkey, content: 'You were invited to collaborate.', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', tags: const [ ['divine', 'collab-invite'], [ @@ -2252,13 +2227,11 @@ void main() { final message = DmMessage( id: '9999999999999999999999999999999999999999999999999999999999999999', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: otherPubkey, content: 'You were invited to collaborate.', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', tags: const [ ['divine', 'collab-invite'], [ @@ -2314,13 +2287,11 @@ void main() { (tester) async { final message = DmMessage( id: '8888888888888888888888888888888888888888888888888888888888888888', - conversationId: - 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + conversationId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', senderPubkey: currentPubkey, content: 'You were invited to collaborate.', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'bbbbbbbbccccccccddddddddeeeeeeeebbbbbbbbccccccccddddddddeeeeeeee', + giftWrapId: 'bbbbbbbbccccccccddddddddeeeeeeeebbbbbbbbccccccccddddddddeeeeeeee', tags: const [ ['divine', 'collab-invite'], [ @@ -2371,15 +2342,13 @@ void main() { (tester) async { final message = DmMessage( id: '7777777777777777777777777777777777777777777777777777777777777777', - conversationId: - 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + conversationId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', senderPubkey: otherPubkey, content: 'You were invited to collaborate on Skate loop. ' 'Open diVine to review and accept.', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'ccccccccddddddddeeeeeeeeffffffff00000000111111112222222233333333', + giftWrapId: 'ccccccccddddddddeeeeeeeeffffffff00000000111111112222222233333333', ); await tester.pumpWidget( @@ -2406,13 +2375,11 @@ void main() { (tester) async { final message = DmMessage( id: '6666666666666666666666666666666666666666666666666666666666666666', - conversationId: - 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + conversationId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', senderPubkey: otherPubkey, content: 'Hey, want to ride together this weekend?', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'ddddddddeeeeeeeeffffffff0000000011111111222222223333333344444444', + giftWrapId: 'ddddddddeeeeeeeeffffffff0000000011111111222222223333333344444444', ); await tester.pumpWidget( @@ -2642,8 +2609,7 @@ void main() { (tester) async { final failedRow = OutgoingDm( id: 'rumor-failed-id', - conversationId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + conversationId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', recipientPubkey: otherPubkey, content: failedSendContent, createdAt: DateTime(2026).millisecondsSinceEpoch ~/ 1000, @@ -2791,15 +2757,13 @@ void main() { ), sibling( id: 'rumor-failed', - recipientPubkey: - '4444444444444444444444444444444444444444444444444444444444444444', + recipientPubkey: '4444444444444444444444444444444444444444444444444444444444444444', recipientWrap: OutgoingWrapStatus.failed, selfWrap: OutgoingWrapStatus.failed, ), sibling( id: 'rumor-delivered-selffailed', - recipientPubkey: - '5555555555555555555555555555555555555555555555555555555555555555', + recipientPubkey: '5555555555555555555555555555555555555555555555555555555555555555', recipientWrap: OutgoingWrapStatus.sent, selfWrap: OutgoingWrapStatus.failed, ), @@ -3148,13 +3112,11 @@ void main() { Future pumpWithMessage(WidgetTester tester) async { final message = DmMessage( id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - conversationId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + conversationId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderPubkey: otherPubkey, content: 'Hello there!', createdAt: now.millisecondsSinceEpoch ~/ 1000, - giftWrapId: - 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', + giftWrapId: 'aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccdddddddd', ); await tester.pumpWidget( @@ -3367,8 +3329,7 @@ void main() { ) async { final message = sharedVideoMessage( senderPubkey: otherPubkey, - replyToId: - '1111111111111111111111111111111111111111111111111111111111111111', + replyToId: '1111111111111111111111111111111111111111111111111111111111111111', ); await tester.pumpWidget( diff --git a/mobile/test/screens/inbox/conversation/widgets/collaborator_invite_card_test.dart b/mobile/test/screens/inbox/conversation/widgets/collaborator_invite_card_test.dart index 725e4eacf6..5325b96632 100644 --- a/mobile/test/screens/inbox/conversation/widgets/collaborator_invite_card_test.dart +++ b/mobile/test/screens/inbox/conversation/widgets/collaborator_invite_card_test.dart @@ -29,8 +29,7 @@ const _creatorPubkey = const _testInvite = CollaboratorInvite( messageId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - videoAddress: - '34236:1122334411223344112233441122334411223344112233441122334411223344:skate-loop', + videoAddress: '34236:1122334411223344112233441122334411223344112233441122334411223344:skate-loop', videoKind: 34236, creatorPubkey: _creatorPubkey, videoDTag: 'skate-loop', diff --git a/mobile/test/screens/inbox/conversation/widgets/message_bubble_test.dart b/mobile/test/screens/inbox/conversation/widgets/message_bubble_test.dart index 983108d87b..add65ddfa8 100644 --- a/mobile/test/screens/inbox/conversation/widgets/message_bubble_test.dart +++ b/mobile/test/screens/inbox/conversation/widgets/message_bubble_test.dart @@ -970,13 +970,11 @@ void main() { // Text.rich wraps our TextSpan in a parent; unwrap to reach // the actual URL spans built by _MessageText. final innerSpan = textSpan.children!.first as TextSpan; - final urlSpan = - innerSpan.children!.firstWhere( - (span) => - span is TextSpan && - (span.text?.contains('https://example.com') ?? false), - ) - as TextSpan; + final urlSpan = innerSpan.children!.firstWhere( + (span) => + span is TextSpan && + (span.text?.contains('https://example.com') ?? false), + ) as TextSpan; expect(urlSpan.recognizer, isA()); }); @@ -1041,13 +1039,10 @@ void main() { final textSpan = richText.text as TextSpan; final innerSpan = textSpan.children!.first as TextSpan; - final urlSpan = - innerSpan.children!.firstWhere( - (span) => - span is TextSpan && - (span.text?.contains('google.com') ?? false), - ) - as TextSpan; + final urlSpan = innerSpan.children!.firstWhere( + (span) => + span is TextSpan && (span.text?.contains('google.com') ?? false), + ) as TextSpan; expect(urlSpan.recognizer, isA()); }); @@ -1079,13 +1074,11 @@ void main() { final textSpan = richText.text as TextSpan; final innerSpan = textSpan.children!.first as TextSpan; - final urlSpan = - innerSpan.children!.firstWhere( - (span) => - span is TextSpan && - (span.text?.contains('example.com/page') ?? false), - ) - as TextSpan; + final urlSpan = innerSpan.children!.firstWhere( + (span) => + span is TextSpan && + (span.text?.contains('example.com/page') ?? false), + ) as TextSpan; expect(urlSpan.recognizer, isA()); }); @@ -1115,13 +1108,11 @@ void main() { final textSpan = richText.text as TextSpan; final innerSpan = textSpan.children!.first as TextSpan; - final emailSpan = - innerSpan.children!.firstWhere( - (span) => - span is TextSpan && - (span.text?.contains('user@example.com') ?? false), - ) - as TextSpan; + final emailSpan = innerSpan.children!.firstWhere( + (span) => + span is TextSpan && + (span.text?.contains('user@example.com') ?? false), + ) as TextSpan; expect(emailSpan.recognizer, isA()); }); diff --git a/mobile/test/screens/inbox/conversation/widgets/reactions_row_test.dart b/mobile/test/screens/inbox/conversation/widgets/reactions_row_test.dart index 06b455bb61..2d89402a22 100644 --- a/mobile/test/screens/inbox/conversation/widgets/reactions_row_test.dart +++ b/mobile/test/screens/inbox/conversation/widgets/reactions_row_test.dart @@ -566,8 +566,7 @@ void main() { picture: picture, rawData: const {}, createdAt: DateTime(2026), - eventId: - 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + eventId: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', ), ), ), diff --git a/mobile/test/screens/inbox/inbox_view_test.dart b/mobile/test/screens/inbox/inbox_view_test.dart index 2e87a9e2b6..945eeb0643 100644 --- a/mobile/test/screens/inbox/inbox_view_test.dart +++ b/mobile/test/screens/inbox/inbox_view_test.dart @@ -567,37 +567,40 @@ void main() { ); }); - testWidgets('shows caught-up state when unread filter is on and everything ' - 'is read', (tester) async { - final conversation = DmConversation( - id: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', - participantPubkeys: const [currentPubkey, otherPubkey], - isGroup: false, - createdAt: nowUnix, - lastMessageContent: 'Hello', - lastMessageTimestamp: nowUnix, - ); + testWidgets( + 'shows caught-up state when unread filter is on and everything ' + 'is read', + (tester) async { + final conversation = DmConversation( + id: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + participantPubkeys: const [currentPubkey, otherPubkey], + isGroup: false, + createdAt: nowUnix, + lastMessageContent: 'Hello', + lastMessageTimestamp: nowUnix, + ); - await tester.pumpWidget( - buildSubject( - state: ConversationListState( - status: ConversationListStatus.loaded, - conversations: [conversation], - filter: InboxFilter.unread, - hasMore: false, + await tester.pumpWidget( + buildSubject( + state: ConversationListState( + status: ConversationListStatus.loaded, + conversations: [conversation], + filter: InboxFilter.unread, + hasMore: false, + ), ), - ), - ); - await tester.pump(); + ); + await tester.pump(); - await tester.tap(find.text('Messages')); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 350)); + await tester.tap(find.text('Messages')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 350)); - final l10n = lookupAppLocalizations(const Locale('en')); - expect(find.text(l10n.inboxUnreadEmptyTitle), findsOneWidget); - expect(find.byType(ConversationTile), findsNothing); - }); + final l10n = lookupAppLocalizations(const Locale('en')); + expect(find.text(l10n.inboxUnreadEmptyTitle), findsOneWidget); + expect(find.byType(ConversationTile), findsNothing); + }, + ); testWidgets('typing in the search bar dispatches ' '$ConversationListSearchQueryChanged', (tester) async { @@ -1933,41 +1936,44 @@ void main() { expect(find.text(l10n.inboxSupportRowTitle), findsOneWidget); }); - testWidgets('distinguishes a retired moderation thread from the live pin', ( - tester, - ) async { - final l10n = lookupAppLocalizations(const Locale('en')); - final retiredConversation = DmConversation( - id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - participantPubkeys: [currentPubkey, kLegacyModerationPubkeys.first], - isGroup: false, - createdAt: nowUnix, - lastMessageContent: 'You can reply to this message to appeal.', - lastMessageTimestamp: nowUnix, - ); + testWidgets( + 'distinguishes a retired moderation thread from the live pin', + ( + tester, + ) async { + final l10n = lookupAppLocalizations(const Locale('en')); + final retiredConversation = DmConversation( + id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + participantPubkeys: [currentPubkey, kLegacyModerationPubkeys.first], + isGroup: false, + createdAt: nowUnix, + lastMessageContent: 'You can reply to this message to appeal.', + lastMessageTimestamp: nowUnix, + ); - await tester.pumpWidget( - buildSubject( - state: ConversationListState( - status: ConversationListStatus.loaded, - conversations: [retiredConversation], - visibleConversations: [retiredConversation], - pinnedSupport: supportPin(), - hasMore: false, + await tester.pumpWidget( + buildSubject( + state: ConversationListState( + status: ConversationListStatus.loaded, + conversations: [retiredConversation], + visibleConversations: [retiredConversation], + pinnedSupport: supportPin(), + hasMore: false, + ), ), - ), - ); - await openMessages(tester); + ); + await openMessages(tester); - expect(find.byType(ConversationTile), findsNWidgets(2)); - expect(find.text(l10n.inboxSupportRowTitle), findsNWidgets(2)); - expect(find.text(l10n.inboxSupportRowSubtitle), findsOneWidget); - expect(find.text(l10n.dmRetiredThreadClosedTitle), findsOneWidget); - expect( - find.text('You can reply to this message to appeal.'), - findsNothing, - ); - }); + expect(find.byType(ConversationTile), findsNWidgets(2)); + expect(find.text(l10n.inboxSupportRowTitle), findsNWidgets(2)); + expect(find.text(l10n.inboxSupportRowSubtitle), findsOneWidget); + expect(find.text(l10n.dmRetiredThreadClosedTitle), findsOneWidget); + expect( + find.text('You can reply to this message to appeal.'), + findsNothing, + ); + }, + ); // #7380: the long-press sheet resolves the peer name on its own, and // that chain omits `moderationDisplayName`. The tile above it reads diff --git a/mobile/test/screens/inbox/message_requests/message_requests_view_test.dart b/mobile/test/screens/inbox/message_requests/message_requests_view_test.dart index 787fd5b847..2cf566fe63 100644 --- a/mobile/test/screens/inbox/message_requests/message_requests_view_test.dart +++ b/mobile/test/screens/inbox/message_requests/message_requests_view_test.dart @@ -202,8 +202,7 @@ void main() { displayName: 'RequestUser', rawData: const {}, createdAt: now, - eventId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + eventId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', ); final request = DmConversation( @@ -396,8 +395,7 @@ void main() { displayName: 'RequestUser', rawData: const {}, createdAt: now, - eventId: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + eventId: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', ); final request = DmConversation( @@ -463,8 +461,7 @@ void main() { () => mockGoRouter.pushNamed( RequestPreviewPage.routeName, pathParameters: { - 'id': - 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'id': 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', }, extra: [otherPubkey], ), diff --git a/mobile/test/screens/inbox/message_requests/request_preview_view_test.dart b/mobile/test/screens/inbox/message_requests/request_preview_view_test.dart index 400b3f1938..e2bb082efe 100644 --- a/mobile/test/screens/inbox/message_requests/request_preview_view_test.dart +++ b/mobile/test/screens/inbox/message_requests/request_preview_view_test.dart @@ -66,8 +66,7 @@ void main() { const fallbackInvite = CollaboratorInvite( messageId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - videoAddress: - '34236:1122334411223344112233441122334411223344112233441122334411223344:skate-loop', + videoAddress: '34236:1122334411223344112233441122334411223344112233441122334411223344:skate-loop', videoKind: 34236, creatorPubkey: otherPubkey, videoDTag: 'skate-loop', @@ -782,8 +781,7 @@ void main() { senderPubkey: otherPubkey, content: 'You were invited to collaborate.', createdAt: 1700000000, - giftWrapId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + giftWrapId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', tags: [ ['divine', 'collab-invite'], [ @@ -853,8 +851,7 @@ void main() { senderPubkey: currentPubkey, content: 'You were invited to collaborate.', createdAt: 1700000000, - giftWrapId: - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + giftWrapId: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', tags: [ ['divine', 'collab-invite'], [ diff --git a/mobile/test/screens/profile_setup_relay_confirmation_test.dart b/mobile/test/screens/profile_setup_relay_confirmation_test.dart index 125cd3e4ef..325f8c06f8 100644 --- a/mobile/test/screens/profile_setup_relay_confirmation_test.dart +++ b/mobile/test/screens/profile_setup_relay_confirmation_test.dart @@ -208,100 +208,102 @@ void main() { }, ); - test('should fail gracefully if relay never returns updated profile', () async { - // BEHAVIOR: If relay doesn't return updated profile after max retries, - // should throw an error instead of navigating with stale data + test( + 'should fail gracefully if relay never returns updated profile', + () async { + // BEHAVIOR: If relay doesn't return updated profile after max retries, + // should throw an error instead of navigating with stale data - // Arrange - final publishedEvent = Event( - testPubkey, - 0, - [], - '{"name":"New Name"}', - createdAt: testTimestamp, - ); + // Arrange + final publishedEvent = Event( + testPubkey, + 0, + [], + '{"name":"New Name"}', + createdAt: testTimestamp, + ); - // Capture the auto-generated event ID - testEventId = publishedEvent.id; + // Capture the auto-generated event ID + testEventId = publishedEvent.id; - when( - () => mockAuthService.createAndSignEvent( - kind: 0, - content: any(named: 'content'), - tags: any(named: 'tags'), - ), - ).thenAnswer((_) async => publishedEvent); - - when( - () => mockNostrService.publishEvent(any()), - ).thenAnswer((_) async => PublishSuccess(event: publishedEvent)); - - // Mock profile repository to ALWAYS return stale profile - final staleProfile = UserProfile( - pubkey: testPubkey, - name: 'Old Name', - createdAt: DateTime.fromMillisecondsSinceEpoch( - (testTimestamp - 60) * 1000, - ), - eventId: 'old-event-id', - rawData: const {'name': 'Old Name'}, - ); - - when( - () => mockProfileRepository.fetchFreshProfile(pubkey: testPubkey), - ).thenAnswer((_) async => staleProfile); - when( - () => mockProfileRepository.deleteCachedProfile(pubkey: testPubkey), - ).thenAnswer((_) async => 1); - - // Act - final event = await mockAuthService.createAndSignEvent( - kind: 0, - content: '{"name":"New Name"}', - tags: [], - ); - - await mockNostrService.publishEvent(event!); - - // Try to get updated profile with retries - UserProfile? confirmedProfile; - var attempts = 0; - const maxAttempts = 3; - - while (attempts < maxAttempts) { - attempts++; - await mockProfileRepository.deleteCachedProfile(pubkey: testPubkey); - final fetchedProfile = await mockProfileRepository.fetchFreshProfile( + when( + () => mockAuthService.createAndSignEvent( + kind: 0, + content: any(named: 'content'), + tags: any(named: 'tags'), + ), + ).thenAnswer((_) async => publishedEvent); + + when( + () => mockNostrService.publishEvent(any()), + ).thenAnswer((_) async => PublishSuccess(event: publishedEvent)); + + // Mock profile repository to ALWAYS return stale profile + final staleProfile = UserProfile( pubkey: testPubkey, + name: 'Old Name', + createdAt: DateTime.fromMillisecondsSinceEpoch( + (testTimestamp - 60) * 1000, + ), + eventId: 'old-event-id', + rawData: const {'name': 'Old Name'}, ); - final eventIdMatches = fetchedProfile?.eventId == testEventId; - final timestampMatches = - fetchedProfile?.createdAt != null && - fetchedProfile!.createdAt.millisecondsSinceEpoch >= - (testTimestamp * 1000 - 1000); + when( + () => mockProfileRepository.fetchFreshProfile(pubkey: testPubkey), + ).thenAnswer((_) async => staleProfile); + when( + () => mockProfileRepository.deleteCachedProfile(pubkey: testPubkey), + ).thenAnswer((_) async => 1); - if (eventIdMatches || timestampMatches) { - confirmedProfile = fetchedProfile; - break; + // Act + final event = await mockAuthService.createAndSignEvent( + kind: 0, + content: '{"name":"New Name"}', + tags: [], + ); + + await mockNostrService.publishEvent(event!); + + // Try to get updated profile with retries + UserProfile? confirmedProfile; + var attempts = 0; + const maxAttempts = 3; + + while (attempts < maxAttempts) { + attempts++; + await mockProfileRepository.deleteCachedProfile(pubkey: testPubkey); + final fetchedProfile = await mockProfileRepository.fetchFreshProfile( + pubkey: testPubkey, + ); + + final eventIdMatches = fetchedProfile?.eventId == testEventId; + final timestampMatches = + fetchedProfile?.createdAt != null && + fetchedProfile!.createdAt.millisecondsSinceEpoch >= + (testTimestamp * 1000 - 1000); + + if (eventIdMatches || timestampMatches) { + confirmedProfile = fetchedProfile; + break; + } } - } - - // Assert - expect( - confirmedProfile, - isNull, - reason: - 'Should not have confirmed profile after max retries with stale data', - ); - expect( - attempts, - equals(maxAttempts), - reason: 'Should exhaust all retry attempts', - ); - - // In real code, this should throw an error to prevent navigation with bad state - }); + + // Assert + expect( + confirmedProfile, + isNull, + reason: 'Should not have confirmed profile after max retries with stale data', + ); + expect( + attempts, + equals(maxAttempts), + reason: 'Should exhaust all retry attempts', + ); + + // In real code, this should throw an error to prevent navigation with bad state + }, + ); test( 'should succeed immediately if first fetch returns updated profile', diff --git a/mobile/test/screens/settings/support_center_screen_test.dart b/mobile/test/screens/settings/support_center_screen_test.dart index 0a886d0396..fcba30cb98 100644 --- a/mobile/test/screens/settings/support_center_screen_test.dart +++ b/mobile/test/screens/settings/support_center_screen_test.dart @@ -444,13 +444,12 @@ void main() { tester, authState: AuthState.unauthenticated, openZendeskSupport: () async => false, - composeEmail: - ({ - required String toEmail, - required String subject, - required String body, - Rect? sharePositionOrigin, - }) async => throw Exception('compose failed'), + composeEmail: ({ + required String toEmail, + required String subject, + required String body, + Rect? sharePositionOrigin, + }) async => throw Exception('compose failed'), ); await tester.tap(find.text(en.supportContactSupport)); diff --git a/mobile/test/screens/sound_detail_screen_test.dart b/mobile/test/screens/sound_detail_screen_test.dart index bc798831d2..efe8639c7c 100644 --- a/mobile/test/screens/sound_detail_screen_test.dart +++ b/mobile/test/screens/sound_detail_screen_test.dart @@ -49,8 +49,7 @@ AudioEvent createTestAudioEvent({ String? title, double? duration, String? url, - String pubkey = - 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + String pubkey = 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', int createdAt = 1700000000, }) { return AudioEvent( @@ -291,8 +290,7 @@ void main() { child: SoundDetailScreen( sound: AudioEvent( id: 'sound1', - pubkey: - 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', createdAt: 1700000000, duration: 6.0, url: 'https://example.com/audio.m4a', @@ -1097,8 +1095,7 @@ void main() { testWidgets('shows snackbar when sound has no URL', (tester) async { final testSound = AudioEvent( id: 'sound1', - pubkey: - 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', createdAt: 1700000000, title: 'No URL Sound', duration: 6.0, diff --git a/mobile/test/screens/user_list_people_screen_test.dart b/mobile/test/screens/user_list_people_screen_test.dart index cb9f5b8f26..020e13845e 100644 --- a/mobile/test/screens/user_list_people_screen_test.dart +++ b/mobile/test/screens/user_list_people_screen_test.dart @@ -169,8 +169,7 @@ void main() { const Stream.empty(), initialState: PeopleListsState( status: PeopleListsStatus.ready, - ownerPubkey: - 'f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0', + ownerPubkey: 'f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0', lists: [list], ), ); @@ -289,8 +288,7 @@ void main() { const Stream.empty(), initialState: PeopleListsState( status: PeopleListsStatus.ready, - ownerPubkey: - 'f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0', + ownerPubkey: 'f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0', lists: [list], ), ); @@ -333,8 +331,7 @@ void main() { const Stream.empty(), initialState: PeopleListsState( status: PeopleListsStatus.ready, - ownerPubkey: - 'f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0', + ownerPubkey: 'f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0', lists: [list], ), ); @@ -861,59 +858,61 @@ void main() { }); group('GoRouter /people-lists/:listId', () { - testWidgets('route uses handwritten GoRoute and resolves listId path param', ( - tester, - ) async { - final bloc = _MockPeopleListsBloc(); - final list = _buildList(id: 'routed-list', name: 'Routed List'); - whenListen( - bloc, - const Stream.empty(), - initialState: PeopleListsState( - status: PeopleListsStatus.ready, - ownerPubkey: - 'bb11cc22dd33ee44ff55aa66bb11cc22dd33ee44ff55aa66bb11cc22dd33ee44', - lists: [list], - ), - ); - - final router = GoRouter( - initialLocation: '/people-lists/${Uri.encodeComponent(list.id)}', - routes: [ - GoRoute( - path: UserListPeopleScreen.path, - name: UserListPeopleScreen.routeName, - builder: (context, state) { - final listId = state.pathParameters['listId']; - if (listId == null || listId.isEmpty) { - return const Scaffold( - body: Center(child: Text('Invalid list')), - ); - } - return UserListPeopleScreen(listId: listId); - }, + testWidgets( + 'route uses handwritten GoRoute and resolves listId path param', + ( + tester, + ) async { + final bloc = _MockPeopleListsBloc(); + final list = _buildList(id: 'routed-list', name: 'Routed List'); + whenListen( + bloc, + const Stream.empty(), + initialState: PeopleListsState( + status: PeopleListsStatus.ready, + ownerPubkey: 'bb11cc22dd33ee44ff55aa66bb11cc22dd33ee44ff55aa66bb11cc22dd33ee44', + lists: [list], ), - ], - ); + ); - await tester.pumpWidget( - testProviderScope( - child: BlocProvider.value( - value: bloc, - child: MaterialApp.router( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - routerConfig: router, + final router = GoRouter( + initialLocation: '/people-lists/${Uri.encodeComponent(list.id)}', + routes: [ + GoRoute( + path: UserListPeopleScreen.path, + name: UserListPeopleScreen.routeName, + builder: (context, state) { + final listId = state.pathParameters['listId']; + if (listId == null || listId.isEmpty) { + return const Scaffold( + body: Center(child: Text('Invalid list')), + ); + } + return UserListPeopleScreen(listId: listId); + }, + ), + ], + ); + + await tester.pumpWidget( + testProviderScope( + child: BlocProvider.value( + value: bloc, + child: MaterialApp.router( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + routerConfig: router, + ), ), ), - ), - ); + ); - await tester.pump(); + await tester.pump(); - expect(find.byType(UserListPeopleScreen), findsOneWidget); - expect(find.text('Routed List'), findsOneWidget); - }); + expect(find.byType(UserListPeopleScreen), findsOneWidget); + expect(find.text('Routed List'), findsOneWidget); + }, + ); testWidgets( 'route falls back to invalid-list scaffold with back button for ' diff --git a/mobile/test/screens/video_metadata/video_metadata_preview_screen_test.dart b/mobile/test/screens/video_metadata/video_metadata_preview_screen_test.dart index 3dc68573c9..e48afbe6dc 100644 --- a/mobile/test/screens/video_metadata/video_metadata_preview_screen_test.dart +++ b/mobile/test/screens/video_metadata/video_metadata_preview_screen_test.dart @@ -195,11 +195,9 @@ void main() { home: Scaffold( body: VideoOverlayActions.preview( previewData: VideoOverlayPreviewData( - pubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + pubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', title: 'A title', - description: - 'description with nostr:npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', + description: 'description with nostr:npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', ), isVisible: true, isActive: true, diff --git a/mobile/test/screens/video_metadata_screen_test.dart b/mobile/test/screens/video_metadata_screen_test.dart index 2d0d3b914a..2d117d1430 100644 --- a/mobile/test/screens/video_metadata_screen_test.dart +++ b/mobile/test/screens/video_metadata_screen_test.dart @@ -478,16 +478,15 @@ void main() { // A re-sign that never resolves keeps isProcessing observably true, // which distinguishes retryC2paSigning (sets it) from a mis-wire to // acknowledgeC2paSigningFailure (which would not). - NativeProofModeService.proofFileOverride = - ( - file, { - required enableAdvancedCawgEmbedding, - creatorBindingAssertion, - cawgIdentityAssertion, - verifiedIdentityBundle, - clips, - editorStateHistory, - }) => Completer().future; + NativeProofModeService.proofFileOverride = ( + file, { + required enableAdvancedCawgEmbedding, + creatorBindingAssertion, + cawgIdentityAssertion, + verifiedIdentityBundle, + clips, + editorStateHistory, + }) => Completer().future; addTearDown(() => NativeProofModeService.proofFileOverride = null); final container = ProviderContainer( @@ -561,16 +560,15 @@ void main() { (tester) async { // A re-sign that never resolves keeps isProcessing observably true, // distinguishing retryC2paSigning from a silent postAnyway. - NativeProofModeService.proofFileOverride = - ( - file, { - required enableAdvancedCawgEmbedding, - creatorBindingAssertion, - cawgIdentityAssertion, - verifiedIdentityBundle, - clips, - editorStateHistory, - }) => Completer().future; + NativeProofModeService.proofFileOverride = ( + file, { + required enableAdvancedCawgEmbedding, + creatorBindingAssertion, + cawgIdentityAssertion, + verifiedIdentityBundle, + clips, + editorStateHistory, + }) => Completer().future; addTearDown(() => NativeProofModeService.proofFileOverride = null); final container = ProviderContainer( diff --git a/mobile/test/services/analytics_service_test.dart b/mobile/test/services/analytics_service_test.dart index 1db2270263..42803d924a 100644 --- a/mobile/test/services/analytics_service_test.dart +++ b/mobile/test/services/analytics_service_test.dart @@ -268,8 +268,7 @@ void main() { 'consent_category': 'product_analytics', 'event_name': 'content_impression_recorded', 'properties': { - 'content_id': - '4444444444444444444444444444444444444444444444444444444444444444', + 'content_id': '4444444444444444444444444444444444444444444444444444444444444444', 'surface': 'feed', 'position': 3, 'visible_ms': 1500, @@ -301,8 +300,7 @@ void main() { backgroundActivityManager: BackgroundActivityManager(), productEventQueue: queue, productAnalyticsEnabled: true, - currentUserPubkey: () => - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + currentUserPubkey: () => '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', anonymousId: () => '018ff7d7-2ef5-7000-8000-000000000001', sessionId: () => '018ff7d7-2ef5-7000-8000-000000000002', platform: () => 'ios', @@ -312,8 +310,7 @@ void main() { await analyticsService.initialize(); final eventId = await analyticsService.recordContentImpression( - contentId: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + contentId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', surface: ProductAnalyticsV2Surface.feed, position: 4, visibleMs: 1000, @@ -804,8 +801,7 @@ void main() { await analyticsService.trackDetailedVideoViewWithUser( video, - userId: - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + userId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', source: 'mobile', eventType: 'view_end', watchDuration: const Duration(seconds: 2), @@ -997,8 +993,7 @@ void main() { await expectLater( analyticsService.trackDetailedVideoViewWithUser( video, - userId: - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + userId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', source: 'mobile', eventType: 'view_end', watchDuration: const Duration(seconds: 2), @@ -1126,8 +1121,7 @@ void main() { return service.trackDetailedVideoViewWithUser( VideoEvent( id: '22e73ca1faedb07dd3e24c1dca52d849aa75c6e4090eb60c532820b782c93da3', - pubkey: - 'ae73ca1faedb07dd3e24c1dca52d849aa75c6e4090eb60c532820b782c93da3', + pubkey: 'ae73ca1faedb07dd3e24c1dca52d849aa75c6e4090eb60c532820b782c93da3', createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, content: 'Test video', timestamp: DateTime.now(), diff --git a/mobile/test/services/auth/nostr_connect_coordinator_test.dart b/mobile/test/services/auth/nostr_connect_coordinator_test.dart index c53f6e2fcb..5f53250ff3 100644 --- a/mobile/test/services/auth/nostr_connect_coordinator_test.dart +++ b/mobile/test/services/auth/nostr_connect_coordinator_test.dart @@ -56,13 +56,12 @@ void main() { onConnectFailed: connectFailedErrors.add, onWaitStarted: () => waitStartedCalls++, onWaitFailed: () => waitFailedCalls++, - reportError: - ( - error, - stack, { - required String reason, - required String logMessage, - }) => reportedReasons.add(reason), + reportError: ( + error, + stack, { + required String reason, + required String logMessage, + }) => reportedReasons.add(reason), sessionFactory: (relays) { createdRelays = relays; return session; diff --git a/mobile/test/services/bug_report_service_test.dart b/mobile/test/services/bug_report_service_test.dart index 095145baf7..33bd9e6446 100644 --- a/mobile/test/services/bug_report_service_test.dart +++ b/mobile/test/services/bug_report_service_test.dart @@ -305,10 +305,8 @@ void main() { recentLogs: [], errorCounts: {}, additionalContext: { - 'eventId': - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - 'pubkeyHex': - 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', + 'eventId': '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + 'pubkeyHex': 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', }, ); diff --git a/mobile/test/services/c2pa_identity_manifest_service_test.dart b/mobile/test/services/c2pa_identity_manifest_service_test.dart index 4ca9f72492..ff5d2a98af 100644 --- a/mobile/test/services/c2pa_identity_manifest_service_test.dart +++ b/mobile/test/services/c2pa_identity_manifest_service_test.dart @@ -61,36 +61,38 @@ void main() { }, ); - test('includes creator binding and final cawg assertions when available', () { - final result = service.buildCreatedVideoManifest( - claimGenerator: 'DiVine/1.0', - title: 'test.mp4', - sourceType: DigitalSourceType.digitalCapture, - creatorBindingAssertion: const NostrCreatorBindingAssertion( - assertionLabel: 'video.divine.nostr.creator_binding', - payloadJson: - '{"version":1,"pubkey":"abc","sig_alg":"nostr.secp256k1","created_at":"2026-03-29T08:30:00.000Z","claims":{},"referenced_assertions":["c2pa.hash.data"],"hard_binding":{"alg":"sha256","value":"deadbeef"},"signature":"cafebabe"}', - signature: 'cafebabe', - pubkey: 'abc', - ), - cawgIdentityAssertion: const { - 'issuer': 'verifyer.divine.video', - 'verified_claims': >[ - {'type': 'nip05', 'value': 'alice@example.com'}, - ], - }, - ); + test( + 'includes creator binding and final cawg assertions when available', + () { + final result = service.buildCreatedVideoManifest( + claimGenerator: 'DiVine/1.0', + title: 'test.mp4', + sourceType: DigitalSourceType.digitalCapture, + creatorBindingAssertion: const NostrCreatorBindingAssertion( + assertionLabel: 'video.divine.nostr.creator_binding', + payloadJson: '{"version":1,"pubkey":"abc","sig_alg":"nostr.secp256k1","created_at":"2026-03-29T08:30:00.000Z","claims":{},"referenced_assertions":["c2pa.hash.data"],"hard_binding":{"alg":"sha256","value":"deadbeef"},"signature":"cafebabe"}', + signature: 'cafebabe', + pubkey: 'abc', + ), + cawgIdentityAssertion: const { + 'issuer': 'verifyer.divine.video', + 'verified_claims': >[ + {'type': 'nip05', 'value': 'alice@example.com'}, + ], + }, + ); - final json = jsonDecode(result.manifestJson) as Map; - final assertions = json['assertions'] as List; - final labels = assertions - .map((assertion) => (assertion as Map)['label']) - .toList(); + final json = jsonDecode(result.manifestJson) as Map; + final assertions = json['assertions'] as List; + final labels = assertions + .map((assertion) => (assertion as Map)['label']) + .toList(); - expect(result.requiresAdvancedEmbedding, isFalse); - expect(labels, contains('video.divine.nostr.creator_binding')); - expect(labels, contains('cawg.identity')); - }); + expect(result.requiresAdvancedEmbedding, isFalse); + expect(labels, contains('video.divine.nostr.creator_binding')); + expect(labels, contains('cawg.identity')); + }, + ); test( 'omits cawg overlay when verifier output is absent and flags advanced path ' @@ -102,8 +104,7 @@ void main() { sourceType: DigitalSourceType.digitalCapture, creatorBindingAssertion: const NostrCreatorBindingAssertion( assertionLabel: 'video.divine.nostr.creator_binding', - payloadJson: - '{"version":1,"pubkey":"abc","sig_alg":"nostr.secp256k1","created_at":"2026-03-29T08:30:00.000Z","claims":{},"referenced_assertions":["c2pa.hash.data"],"hard_binding":{"alg":"sha256","value":"deadbeef"},"signature":"cafebabe"}', + payloadJson: '{"version":1,"pubkey":"abc","sig_alg":"nostr.secp256k1","created_at":"2026-03-29T08:30:00.000Z","claims":{},"referenced_assertions":["c2pa.hash.data"],"hard_binding":{"alg":"sha256","value":"deadbeef"},"signature":"cafebabe"}', signature: 'cafebabe', pubkey: 'abc', ), diff --git a/mobile/test/services/cache_recovery_service_test.dart b/mobile/test/services/cache_recovery_service_test.dart index 57a5e83b8a..822a3a82ff 100644 --- a/mobile/test/services/cache_recovery_service_test.dart +++ b/mobile/test/services/cache_recovery_service_test.dart @@ -83,8 +83,7 @@ void main() { : await Hive.openBox(HiveBoxNames.hashtagStats); final upload = PendingUpload.create( localVideoPath: '/tmp/durable-upload.mp4', - nostrPubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nostrPubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ); await pendingUploads.put(upload.id, upload); await hashtagStats.put('popular_hashtags', ['divine']); diff --git a/mobile/test/services/cawg_verifier_client_test.dart b/mobile/test/services/cawg_verifier_client_test.dart index f344475ac0..67bac7bd74 100644 --- a/mobile/test/services/cawg_verifier_client_test.dart +++ b/mobile/test/services/cawg_verifier_client_test.dart @@ -119,8 +119,7 @@ void main() { final bundle = await client.verifyClaims( const VerifierClaimRequest( - pubkey: - '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', + pubkey: '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', socialHandles: [ VerifierSocialHandleClaim( platform: 'github', @@ -166,8 +165,7 @@ void main() { final bundle = await client.verifyClaims( const VerifierClaimRequest( - pubkey: - '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', + pubkey: '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', ), ); diff --git a/mobile/test/services/content_filter_service_test.dart b/mobile/test/services/content_filter_service_test.dart index ee5c438e0a..b96726671b 100644 --- a/mobile/test/services/content_filter_service_test.dart +++ b/mobile/test/services/content_filter_service_test.dart @@ -782,9 +782,9 @@ void main() { } final prefs = await SharedPreferences.getInstance(); - final persisted = - jsonDecode(prefs.getString('content_filter_prefs')!) - as Map; + final persisted = jsonDecode( + prefs.getString('content_filter_prefs')!, + ) as Map; expect(persisted['drugs'], equals('hide')); expect(persisted['violence'], equals('hide')); expect(persisted['ai-generated'], equals('hide')); diff --git a/mobile/test/services/content_reporting_service_test.dart b/mobile/test/services/content_reporting_service_test.dart index 55031a0676..9c02e06b1d 100644 --- a/mobile/test/services/content_reporting_service_test.dart +++ b/mobile/test/services/content_reporting_service_test.dart @@ -1603,8 +1603,7 @@ void main() { final _ = await service.reportContent( eventId: 'user_not_a_real_event_id', - authorPubkey: - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + authorPubkey: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', reason: ContentFilterReason.other, details: 'User-only report preserved for local history', ); diff --git a/mobile/test/services/curated_list_service_collaboration_test.dart b/mobile/test/services/curated_list_service_collaboration_test.dart index 3ce706c220..e687167bab 100644 --- a/mobile/test/services/curated_list_service_collaboration_test.dart +++ b/mobile/test/services/curated_list_service_collaboration_test.dart @@ -28,8 +28,7 @@ void main() { registerFallbackValue( Event.fromJson({ 'id': 'fallback_event_id', - 'pubkey': - 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', + 'pubkey': 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', 'created_at': 0, 'kind': 1, 'tags': >[], diff --git a/mobile/test/services/curated_list_service_initialization_test.dart b/mobile/test/services/curated_list_service_initialization_test.dart index 093bb75103..73938b0a17 100644 --- a/mobile/test/services/curated_list_service_initialization_test.dart +++ b/mobile/test/services/curated_list_service_initialization_test.dart @@ -31,8 +31,7 @@ void main() { registerFallbackValue( Event.fromJson({ 'id': 'fallback_event_id', - 'pubkey': - 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', + 'pubkey': 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', 'created_at': 0, 'kind': 1, 'tags': >[], diff --git a/mobile/test/services/curated_list_service_persistence_test.dart b/mobile/test/services/curated_list_service_persistence_test.dart index 37f20852c2..2abe508e0e 100644 --- a/mobile/test/services/curated_list_service_persistence_test.dart +++ b/mobile/test/services/curated_list_service_persistence_test.dart @@ -30,8 +30,7 @@ void main() { registerFallbackValue( Event.fromJson({ 'id': 'fallback_event_id', - 'pubkey': - 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', + 'pubkey': 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', 'created_at': 0, 'kind': 1, 'tags': >[], diff --git a/mobile/test/services/curated_list_service_playlist_test.dart b/mobile/test/services/curated_list_service_playlist_test.dart index a472522fcb..097d6d38fb 100644 --- a/mobile/test/services/curated_list_service_playlist_test.dart +++ b/mobile/test/services/curated_list_service_playlist_test.dart @@ -29,8 +29,7 @@ void main() { registerFallbackValue( Event.fromJson({ 'id': 'fallback_event_id', - 'pubkey': - 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', + 'pubkey': 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', 'created_at': 0, 'kind': 1, 'tags': >[], diff --git a/mobile/test/services/curated_list_service_query_test.dart b/mobile/test/services/curated_list_service_query_test.dart index 3c693f8293..2b37db6089 100644 --- a/mobile/test/services/curated_list_service_query_test.dart +++ b/mobile/test/services/curated_list_service_query_test.dart @@ -32,8 +32,7 @@ void main() { registerFallbackValue( Event.fromJson({ 'id': 'fallback_event_id', - 'pubkey': - 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', + 'pubkey': 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', 'created_at': 0, 'kind': 1, 'tags': >[], diff --git a/mobile/test/services/curated_list_service_stream_test.dart b/mobile/test/services/curated_list_service_stream_test.dart index 5d8bf5bfb0..ee8c8ecdac 100644 --- a/mobile/test/services/curated_list_service_stream_test.dart +++ b/mobile/test/services/curated_list_service_stream_test.dart @@ -73,8 +73,7 @@ void main() { registerFallbackValue( Event.fromJson({ 'id': 'fallback_event_id', - 'pubkey': - 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', + 'pubkey': 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', 'created_at': 0, 'kind': 1, 'tags': >[], diff --git a/mobile/test/services/curated_list_service_video_management_test.dart b/mobile/test/services/curated_list_service_video_management_test.dart index a4fcb66180..7d59df2e1d 100644 --- a/mobile/test/services/curated_list_service_video_management_test.dart +++ b/mobile/test/services/curated_list_service_video_management_test.dart @@ -30,8 +30,7 @@ void main() { registerFallbackValue( Event.fromJson({ 'id': 'fallback_event_id', - 'pubkey': - 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', + 'pubkey': 'aabbccdd00112233445566778899aabbccdd00112233445566778899aabbccdd', 'created_at': 0, 'kind': 1, 'tags': >[], diff --git a/mobile/test/services/media_auth_interceptor_test.dart b/mobile/test/services/media_auth_interceptor_test.dart index 46b0cbdcb6..3c85194438 100644 --- a/mobile/test/services/media_auth_interceptor_test.dart +++ b/mobile/test/services/media_auth_interceptor_test.dart @@ -462,16 +462,14 @@ void main() { ); final result = await interceptor.createPassiveAuthHeadersForAdultMedia( - sha256Hash: - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + sha256Hash: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', serverUrl: 'https://media.divine.video', ); expect(result.headersOrNull, equals({'Authorization': 'Nostr token'})); verify( () => mockMediaViewerAuthService.createAuthHeaders( - sha256Hash: - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + sha256Hash: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', serverUrl: 'https://media.divine.video', ), ).called(1); @@ -632,8 +630,7 @@ void main() { expect(result, isA()); verify( () => mockMediaViewerAuthService.createAuthHeaders( - sha256Hash: - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + sha256Hash: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', serverUrl: 'https://media.divine.video', ), ).called(1); diff --git a/mobile/test/services/native_proofmode_service_test.dart b/mobile/test/services/native_proofmode_service_test.dart index bf2c26002e..1ca59fcbb0 100644 --- a/mobile/test/services/native_proofmode_service_test.dart +++ b/mobile/test/services/native_proofmode_service_test.dart @@ -370,8 +370,7 @@ class _FailingC2paSigningService extends C2paSigningService { return C2paSigningResult( signedFilePath: this.videoPath, success: false, - error: - 'PlatformException(C2PA_ERROR, A TLS error caused the secure connection to fail., null, null)', + error: 'PlatformException(C2PA_ERROR, A TLS error caused the secure connection to fail., null, null)', failureReason: C2paSigningFailureReason.tls, ); } diff --git a/mobile/test/services/nip07_service_test.dart b/mobile/test/services/nip07_service_test.dart index dd8d7751d7..07001d96fd 100644 --- a/mobile/test/services/nip07_service_test.dart +++ b/mobile/test/services/nip07_service_test.dart @@ -129,8 +129,7 @@ void main() { await service.loadUserRelays(); expect(service.userRelays, isNotNull); - extension.publicKey = - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + extension.publicKey = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; await service.connect(); expect(service.userRelays, isNull); diff --git a/mobile/test/services/nip98_auth_service_test.dart b/mobile/test/services/nip98_auth_service_test.dart index cc289b3287..9cedfa58db 100644 --- a/mobile/test/services/nip98_auth_service_test.dart +++ b/mobile/test/services/nip98_auth_service_test.dart @@ -48,8 +48,7 @@ void main() { _stubSignEvent(mockAuthService); final token = await service.createAuthToken( - url: - 'https://relay.example.com/api/notifications?limit=50&types=reaction', + url: 'https://relay.example.com/api/notifications?limit=50&types=reaction', method: HttpMethod.get, ); @@ -81,8 +80,7 @@ void main() { _stubSignEvent(mockAuthService); final token = await service.createAuthToken( - url: - 'https://relay.example.com/api/users/pubkey123/notifications/read', + url: 'https://relay.example.com/api/users/pubkey123/notifications/read', method: HttpMethod.post, ); @@ -348,8 +346,7 @@ void main() { method: HttpMethod.get, ); - activePubkey = - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; + activePubkey = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; final accountBToken = await service.createAuthToken( url: 'https://invites.divine.video/v1/invite-status', method: HttpMethod.get, diff --git a/mobile/test/services/nostr_creator_binding_service_test.dart b/mobile/test/services/nostr_creator_binding_service_test.dart index 23827db46e..05cc3c4e9b 100644 --- a/mobile/test/services/nostr_creator_binding_service_test.dart +++ b/mobile/test/services/nostr_creator_binding_service_test.dart @@ -59,8 +59,7 @@ void main() { ), hardBinding: const CreatorBindingHardBinding( alg: 'sha256', - value: - 'ef5d3d4f69d72df6d4d08f625f66ecfb17b3a6dd4e03f6f5a6a5f0e31ecfe8ee', + value: 'ef5d3d4f69d72df6d4d08f625f66ecfb17b3a6dd4e03f6f5a6a5f0e31ecfe8ee', ), referencedAssertions: const [ 'c2pa.actions.v2', @@ -79,8 +78,7 @@ void main() { ), hardBinding: const CreatorBindingHardBinding( alg: 'sha256', - value: - 'ef5d3d4f69d72df6d4d08f625f66ecfb17b3a6dd4e03f6f5a6a5f0e31ecfe8ee', + value: 'ef5d3d4f69d72df6d4d08f625f66ecfb17b3a6dd4e03f6f5a6a5f0e31ecfe8ee', ), referencedAssertions: const [ 'c2pa.actions.v2', @@ -121,8 +119,7 @@ void main() { payload['hard_binding'], equals(const { 'alg': 'sha256', - 'value': - 'ef5d3d4f69d72df6d4d08f625f66ecfb17b3a6dd4e03f6f5a6a5f0e31ecfe8ee', + 'value': 'ef5d3d4f69d72df6d4d08f625f66ecfb17b3a6dd4e03f6f5a6a5f0e31ecfe8ee', }), ); expect(claims['nip05'], equals('alice@example.com')); diff --git a/mobile/test/services/og_viner_cache_service_test.dart b/mobile/test/services/og_viner_cache_service_test.dart index ee34484f86..bf6e5f40b2 100644 --- a/mobile/test/services/og_viner_cache_service_test.dart +++ b/mobile/test/services/og_viner_cache_service_test.dart @@ -60,9 +60,9 @@ void main() { expect(service.isOgViner(ogPubkey), isTrue); expect(service.isOgViner(nonOgPubkey), isFalse); - final stored = - jsonDecode(prefs.getString(ogVinerPubkeysCacheKey)!) - as List; + final stored = jsonDecode( + prefs.getString(ogVinerPubkeysCacheKey)!, + ) as List; expect(stored, [ogPubkey]); }, ); diff --git a/mobile/test/services/performance_monitoring_service_test.dart b/mobile/test/services/performance_monitoring_service_test.dart index 0ab1ac2c0d..bb46e6a6ff 100644 --- a/mobile/test/services/performance_monitoring_service_test.dart +++ b/mobile/test/services/performance_monitoring_service_test.dart @@ -124,8 +124,7 @@ void main() { r'baseConfigurationReference = [0-9A-F]+ /\* Profile\.xcconfig \*/;', ).hasMatch(pbxproj), isTrue, - reason: - 'Runner target Profile configuration must point at Profile.xcconfig.', + reason: 'Runner target Profile configuration must point at Profile.xcconfig.', ); }); }); diff --git a/mobile/test/services/saved_sounds_service_test.dart b/mobile/test/services/saved_sounds_service_test.dart index c8a02f7e8d..c147c02058 100644 --- a/mobile/test/services/saved_sounds_service_test.dart +++ b/mobile/test/services/saved_sounds_service_test.dart @@ -22,8 +22,7 @@ AudioEvent _sound({ }) { return AudioEvent( id: id, - pubkey: - 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', createdAt: createdAt, title: title ?? 'Test Sound $id', duration: 6, @@ -175,11 +174,9 @@ void main() { await service.saveSavedSound(record); - final raw = - jsonDecode( - sharedPreferences.getString(service.storageKey)!, - ) - as Map; + final raw = jsonDecode( + sharedPreferences.getString(service.storageKey)!, + ) as Map; expect( raw['schemaVersion'], SavedSoundLibraryPayload.currentSchemaVersion, @@ -768,11 +765,9 @@ void main() { filePath: '/documents/draft_audio_imports/d1/good.m4a', ), ); - final stored = - jsonDecode( - sharedPreferences.getString('saved_reusable_sounds_anon')!, - ) - as Map; + final stored = jsonDecode( + sharedPreferences.getString('saved_reusable_sounds_anon')!, + ) as Map; (stored['sounds'] as List).add({'audio': 'not a map'}); await sharedPreferences.setString( 'saved_reusable_sounds_anon', diff --git a/mobile/test/services/subscription_deduplication_test.dart b/mobile/test/services/subscription_deduplication_test.dart index 9445f5a8d1..1ea75dc0c4 100644 --- a/mobile/test/services/subscription_deduplication_test.dart +++ b/mobile/test/services/subscription_deduplication_test.dart @@ -188,8 +188,7 @@ void main() { expect( hash1, isNot(equals(hash2)), - reason: - 'Adding time constraints should generate different subscription IDs', + reason: 'Adding time constraints should generate different subscription IDs', ); }); }); diff --git a/mobile/test/services/subtitle_timeline_thumbnail_service_test.dart b/mobile/test/services/subtitle_timeline_thumbnail_service_test.dart index 7f655369f5..228fd78b5a 100644 --- a/mobile/test/services/subtitle_timeline_thumbnail_service_test.dart +++ b/mobile/test/services/subtitle_timeline_thumbnail_service_test.dart @@ -65,21 +65,21 @@ void main() { test('unresolved HLS manifests are not cached or extracted', () async { final service = SubtitleTimelineThumbnailService( - downloadVideo: - ({required String url, required String cacheKey}) async => - fail('HLS manifests must not be cached under the video id'), + downloadVideo: ({ + required String url, + required String cacheKey, + }) async => fail('HLS manifests must not be cached under the video id'), resolveVideoUrl: (url) async => 'https://media.divine.video/video/hls/master.m3u8', - stripThumbnailStreamFactory: - ({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset = Duration.zero, - List? priorityTimestamps, - }) => fail('extraction must not start for an HLS manifest'), + stripThumbnailStreamFactory: ({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset = Duration.zero, + List? priorityTimestamps, + }) => fail('extraction must not start for an HLS manifest'), ); final batches = await service @@ -96,18 +96,19 @@ void main() { test('yields nothing when the video cannot be cached', () async { final service = SubtitleTimelineThumbnailService( - downloadVideo: - ({required String url, required String cacheKey}) async => null, - stripThumbnailStreamFactory: - ({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset = Duration.zero, - List? priorityTimestamps, - }) => fail('extraction must not start without a file'), + downloadVideo: ({ + required String url, + required String cacheKey, + }) async => null, + stripThumbnailStreamFactory: ({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset = Duration.zero, + List? priorityTimestamps, + }) => fail('extraction must not start without a file'), ); final batches = await service @@ -124,19 +125,19 @@ void main() { test('a failed download leaves the timeline without frames', () async { final service = SubtitleTimelineThumbnailService( - downloadVideo: - ({required String url, required String cacheKey}) async => - throw const SocketException('offline'), - stripThumbnailStreamFactory: - ({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset = Duration.zero, - List? priorityTimestamps, - }) => fail('extraction must not start without a file'), + downloadVideo: ({ + required String url, + required String cacheKey, + }) async => throw const SocketException('offline'), + stripThumbnailStreamFactory: ({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset = Duration.zero, + List? priorityTimestamps, + }) => fail('extraction must not start without a file'), ); final batches = await service @@ -153,8 +154,10 @@ void main() { test('a failed extraction keeps the frames that arrived', () async { final service = SubtitleTimelineThumbnailService( - downloadVideo: - ({required String url, required String cacheKey}) async => video, + downloadVideo: ({ + required String url, + required String cacheKey, + }) async => video, stripThumbnailStreamFactory: ({ required String videoPath, @@ -192,9 +195,10 @@ void main() { test('an unknown duration skips extraction entirely', () async { final service = SubtitleTimelineThumbnailService( - downloadVideo: - ({required String url, required String cacheKey}) async => - fail('nothing to extract from, so nothing to download'), + downloadVideo: ({ + required String url, + required String cacheKey, + }) async => fail('nothing to extract from, so nothing to download'), ); final batches = await service diff --git a/mobile/test/services/supporter_api_client_test.dart b/mobile/test/services/supporter_api_client_test.dart index a162bc6dfc..b64f85c458 100644 --- a/mobile/test/services/supporter_api_client_test.dart +++ b/mobile/test/services/supporter_api_client_test.dart @@ -71,8 +71,7 @@ void main() { ); final snapshot = await buildClient().fetchMe( - expectedPubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + expectedPubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ); expect(snapshot.status, SupporterServerStatus.grace); @@ -121,8 +120,7 @@ void main() { idempotencyKey: 'attempt-1234567890', proof: proof, ), - expectedPubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + expectedPubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ); expect(jsonDecode(requestBody), { @@ -140,11 +138,11 @@ void main() { final client = SupporterApiClient( baseUri: Uri.parse('https://supporters.test'), httpClient: httpClient, - authHeaderProvider: ({required url, required method, payload}) async => ( - authorizationHeader: 'Nostr stale-token', - pubkey: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', - ), + authHeaderProvider: ({required url, required method, payload}) async => + ( + authorizationHeader: 'Nostr stale-token', + pubkey: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + ), ); await expectLater( @@ -155,8 +153,7 @@ void main() { idempotencyKey: 'attempt-1234567890', proof: {'signed_payload': 'opaque-proof-material'}, ), - expectedPubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + expectedPubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), throwsA( isA().having( @@ -175,41 +172,46 @@ void main() { ); }); - test('rejects reads and preference writes signed by another account', () async { - final client = SupporterApiClient( - baseUri: Uri.parse('https://supporters.test'), - httpClient: httpClient, - authHeaderProvider: ({required url, required method, payload}) async => ( - authorizationHeader: 'Nostr stale-token', - pubkey: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', - ), - ); - const expectedPubkey = - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + test( + 'rejects reads and preference writes signed by another account', + () async { + final client = SupporterApiClient( + baseUri: Uri.parse('https://supporters.test'), + httpClient: httpClient, + authHeaderProvider: + ({required url, required method, payload}) async => ( + authorizationHeader: 'Nostr stale-token', + pubkey: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + ), + ); + const expectedPubkey = + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; - await expectLater( - client.fetchMe(expectedPubkey: expectedPubkey), - throwsA(isA()), - ); - await expectLater( - client.updateRecognition( - expectedPubkey: expectedPubkey, - haloVisible: true, - discoveryVisible: false, - foundingHistoryVisible: false, - ), - throwsA(isA()), - ); - verifyNever(() => httpClient.get(any(), headers: any(named: 'headers'))); - verifyNever( - () => httpClient.patch( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ); - }); + await expectLater( + client.fetchMe(expectedPubkey: expectedPubkey), + throwsA(isA()), + ); + await expectLater( + client.updateRecognition( + expectedPubkey: expectedPubkey, + haloVisible: true, + discoveryVisible: false, + foundingHistoryVisible: false, + ), + throwsA(isA()), + ); + verifyNever( + () => httpClient.get(any(), headers: any(named: 'headers')), + ); + verifyNever( + () => httpClient.patch( + any(), + headers: any(named: 'headers'), + body: any(named: 'body'), + ), + ); + }, + ); test('maps ownership conflict to a typed API failure', () async { when( @@ -235,8 +237,7 @@ void main() { idempotencyKey: 'attempt-1234567890', proof: {'purchase_token': 'opaque'}, ), - expectedPubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + expectedPubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), throwsA( isA().having( @@ -272,8 +273,7 @@ void main() { idempotencyKey: 'attempt-1234567890', proof: {'signed_payload': 'opaque-proof-material'}, ), - expectedPubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + expectedPubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), throwsA( isA().having( diff --git a/mobile/test/services/upload_initialization_helper_test.dart b/mobile/test/services/upload_initialization_helper_test.dart index 5c0d268707..e2adaab345 100644 --- a/mobile/test/services/upload_initialization_helper_test.dart +++ b/mobile/test/services/upload_initialization_helper_test.dart @@ -7,6 +7,7 @@ library; import 'dart:io'; + import 'package:flutter_test/flutter_test.dart'; import 'package:hive_ce_flutter/hive_flutter.dart'; import 'package:openvine/models/pending_upload.dart'; diff --git a/mobile/test/services/upload_manager_error_handling_test.dart b/mobile/test/services/upload_manager_error_handling_test.dart index 1e7633458f..a9a53df1aa 100644 --- a/mobile/test/services/upload_manager_error_handling_test.dart +++ b/mobile/test/services/upload_manager_error_handling_test.dart @@ -942,14 +942,12 @@ void main() { expect( failedUpload.retryCount, equals(0), - reason: - 'auto-attempts must not mutate retryCount — it is the manual-retry budget', + reason: 'auto-attempts must not mutate retryCount — it is the manual-retry budget', ); expect( failedUpload.canRetry, isTrue, - reason: - 'user must still be able to manually retry after auto-retry exhaustion', + reason: 'user must still be able to manually retry after auto-retry exhaustion', ); // Verify retryUpload() actually re-activates the upload and does not diff --git a/mobile/test/services/upload_manager_sandbox_test.dart b/mobile/test/services/upload_manager_sandbox_test.dart index 615ab71cc2..b22432e654 100644 --- a/mobile/test/services/upload_manager_sandbox_test.dart +++ b/mobile/test/services/upload_manager_sandbox_test.dart @@ -7,6 +7,7 @@ library; import 'dart:io'; + import 'package:flutter_test/flutter_test.dart'; import 'package:hive_ce_flutter/hive_flutter.dart'; import 'package:openvine/services/upload_initialization_helper.dart'; diff --git a/mobile/test/services/video_clip_import_service_test.dart b/mobile/test/services/video_clip_import_service_test.dart index ea4f6f3cae..67133cba6b 100644 --- a/mobile/test/services/video_clip_import_service_test.dart +++ b/mobile/test/services/video_clip_import_service_test.dart @@ -250,8 +250,7 @@ void main() { test('normalizes whitespace and truncates long library titles', () { final title = VideoClipImportService.defaultLibraryTitleFor( _video( - title: - ' This title has extra spaces and it keeps going past the eighty character limit for local clip names ', + title: ' This title has extra spaces and it keeps going past the eighty character limit for local clip names ', content: 'fallback description', ), ); diff --git a/mobile/test/services/video_editor/clip_thumbnail_manager_test.dart b/mobile/test/services/video_editor/clip_thumbnail_manager_test.dart index 53d2375c17..ce288cbd63 100644 --- a/mobile/test/services/video_editor/clip_thumbnail_manager_test.dart +++ b/mobile/test/services/video_editor/clip_thumbnail_manager_test.dart @@ -259,96 +259,93 @@ void main() { expect(() => manager['tgt'], throwsA(isA())); }); - test( - 'borrows source files without copying and protects them from ' - 'source cleanup', - () async { - final tempDir = Directory.systemTemp.createTempSync( - 'clip_thumbnail_seed_test_', - ); - addTearDown(() { - if (tempDir.existsSync()) { - tempDir.deleteSync(recursive: true); - } - }); - - final borrowedThumbnail = File('${tempDir.path}/borrowed.jpg') - ..writeAsStringSync('borrowed-thumbnail'); - final unborrowedThumbnail = File('${tempDir.path}/unborrowed.jpg') - ..writeAsStringSync('unborrowed-thumbnail'); - final sourceVideoPath = '${tempDir.path}/source.mp4'; + test('borrows source files without copying and protects them from ' + 'source cleanup', () async { + final tempDir = Directory.systemTemp.createTempSync( + 'clip_thumbnail_seed_test_', + ); + addTearDown(() { + if (tempDir.existsSync()) { + tempDir.deleteSync(recursive: true); + } + }); + + final borrowedThumbnail = File('${tempDir.path}/borrowed.jpg') + ..writeAsStringSync('borrowed-thumbnail'); + final unborrowedThumbnail = File('${tempDir.path}/unborrowed.jpg') + ..writeAsStringSync('unborrowed-thumbnail'); + final sourceVideoPath = '${tempDir.path}/source.mp4'; + + // The seeded target starts a real subscription on the next sync, so + // use a fake stream factory to keep that off the native extractor. + final controllers = >>[]; + final manager = ClipThumbnailManager( + stripThumbnailStreamFactory: + ({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset = Duration.zero, + List? priorityTimestamps, + }) { + final controller = StreamController>(); + controllers.add(controller); + return controller.stream; + }, + ); + addTearDown(() { + manager.dispose(); + for (final controller in controllers) { + unawaited(controller.close()); + } + }); - // The seeded target starts a real subscription on the next sync, so - // use a fake stream factory to keep that off the native extractor. - final controllers = >>[]; - final manager = ClipThumbnailManager( - stripThumbnailStreamFactory: - ({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset = Duration.zero, - List? priorityTimestamps, - }) { - final controller = StreamController>(); - controllers.add(controller); - return controller.stream; - }, - ); - addTearDown(() { - manager.dispose(); - for (final controller in controllers) { - unawaited(controller.close()); - } - }); - - final sourceClip = _createTestClip(id: 'src', seconds: 4); - final targetClip = _createFileClip( - id: 'tgt', - videoPath: sourceVideoPath, - seconds: 2, - ); - manager.sync(clips: [sourceClip], devicePixelRatio: 1); - manager['src'].value = [ - StripThumbnail( - path: borrowedThumbnail.path, - timestamp: const Duration(seconds: 1), - ), - StripThumbnail( - path: unborrowedThumbnail.path, - timestamp: const Duration(seconds: 3), - ), - ]; + final sourceClip = _createTestClip(id: 'src', seconds: 4); + final targetClip = _createFileClip( + id: 'tgt', + videoPath: sourceVideoPath, + seconds: 2, + ); + manager.sync(clips: [sourceClip], devicePixelRatio: 1); + manager['src'].value = [ + StripThumbnail( + path: borrowedThumbnail.path, + timestamp: const Duration(seconds: 1), + ), + StripThumbnail( + path: unborrowedThumbnail.path, + timestamp: const Duration(seconds: 3), + ), + ]; - manager.seedFromSource( - sourceClipId: 'src', - targetClipId: 'tgt', - sourceRange: const DurationRange( - start: Duration.zero, - end: Duration(seconds: 2), - ), - currentSourcePath: sourceVideoPath, - ); + manager.seedFromSource( + sourceClipId: 'src', + targetClipId: 'tgt', + sourceRange: const DurationRange( + start: Duration.zero, + end: Duration(seconds: 2), + ), + currentSourcePath: sourceVideoPath, + ); - // Borrowed, not copied — the image cache entry decoded for the - // source tile stays valid, so the new tile paints instantly - // instead of flashing black on a cold decode. - final seededPath = manager['tgt'].value.single.path; - expect(seededPath, equals(borrowedThumbnail.path)); - - // Simulate the split lifecycle: the source clip is removed, but - // the seeded target still points at the source video until render - // completes. Borrowed files must survive stale source cleanup; - // files nobody borrowed are deleted. - manager.sync(clips: [targetClip], devicePixelRatio: 1); - await Future.delayed(Duration.zero); - - expect(borrowedThumbnail.existsSync(), isTrue); - expect(unborrowedThumbnail.existsSync(), isFalse); - }, - ); + // Borrowed, not copied — the image cache entry decoded for the + // source tile stays valid, so the new tile paints instantly + // instead of flashing black on a cold decode. + final seededPath = manager['tgt'].value.single.path; + expect(seededPath, equals(borrowedThumbnail.path)); + + // Simulate the split lifecycle: the source clip is removed, but + // the seeded target still points at the source video until render + // completes. Borrowed files must survive stale source cleanup; + // files nobody borrowed are deleted. + manager.sync(clips: [targetClip], devicePixelRatio: 1); + await Future.delayed(Duration.zero); + + expect(borrowedThumbnail.existsSync(), isTrue); + expect(unborrowedThumbnail.existsSync(), isFalse); + }); test('can preserve source timestamps while filtering a range', () { final sourceClip = _createTestClip(id: 'src', seconds: 6); @@ -578,10 +575,7 @@ void main() { final afterBatch1 = fakeStreamManager['end'].value; expect(afterBatch1, hasLength(7)); - expect( - afterBatch1.map((t) => t.path), - contains(fresh1.path), - ); + expect(afterBatch1.map((t) => t.path), contains(fresh1.path)); // Every borrowed file survives — even the displaced seed's — since // the retired source strip still references them for undo. expect(borrowedFiles[1].existsSync(), isTrue); @@ -693,10 +687,7 @@ void main() { ]); await pumpEventQueue(); - expect( - fakeStreamManager['a'].value.single.path, - equals(freshA.path), - ); + expect(fakeStreamManager['a'].value.single.path, equals(freshA.path)); expect(shared.existsSync(), isTrue); expect(freshA.existsSync(), isTrue); }, @@ -773,117 +764,111 @@ void main() { }, ); - test( - 'seeds the target empty when the source has no thumbnails yet, then ' - 'still starts the subscription on the next sync', - () async { - final sourceVideoPath = '${tempDir.path}/empty_source.mp4'; - - final sourceClip = _createFileClip( - id: 'src', - videoPath: sourceVideoPath, - seconds: 10, - ); - fakeStreamManager.sync(clips: [sourceClip], devicePixelRatio: 1); - expect(controllers, hasLength(1)); + test('seeds the target empty when the source has no thumbnails yet, then ' + 'still starts the subscription on the next sync', () async { + final sourceVideoPath = '${tempDir.path}/empty_source.mp4'; - // Source frames haven't been extracted yet — seeding is - // best-effort: the target is created empty but still marked seeded. - fakeStreamManager.seedFromSource( - sourceClipId: 'src', - targetClipId: 'end', - sourceRange: const DurationRange( - start: Duration(seconds: 3), - end: Duration(seconds: 10), - ), - timestampOffset: Duration.zero, - currentSourcePath: sourceVideoPath, - ); - expect(fakeStreamManager['end'].value, isEmpty); + final sourceClip = _createFileClip( + id: 'src', + videoPath: sourceVideoPath, + seconds: 10, + ); + fakeStreamManager.sync(clips: [sourceClip], devicePixelRatio: 1); + expect(controllers, hasLength(1)); - // The next sync starts the real subscription against the shared - // source file even though the seed was empty. - final endClip = _createFileClip( - id: 'end', - videoPath: sourceVideoPath, - seconds: 10, - ); - fakeStreamManager.sync(clips: [endClip], devicePixelRatio: 1); - expect(controllers, hasLength(2)); - }, - ); + // Source frames haven't been extracted yet — seeding is + // best-effort: the target is created empty but still marked seeded. + fakeStreamManager.seedFromSource( + sourceClipId: 'src', + targetClipId: 'end', + sourceRange: const DurationRange( + start: Duration(seconds: 3), + end: Duration(seconds: 10), + ), + timestampOffset: Duration.zero, + currentSourcePath: sourceVideoPath, + ); + expect(fakeStreamManager['end'].value, isEmpty); + + // The next sync starts the real subscription against the shared + // source file even though the seed was empty. + final endClip = _createFileClip( + id: 'end', + videoPath: sourceVideoPath, + seconds: 10, + ); + fakeStreamManager.sync(clips: [endClip], devicePixelRatio: 1); + expect(controllers, hasLength(2)); + }); // Regression: a native extraction failure mid-stream errors the // subscription with only a partial fresh set delivered. That partial // set must not be treated as complete — the carried gap-fillers stay // on screen (and on disk), and a later retire/restore starts a fresh // subscription instead of parking the truncated strip as final. - test( - 'keeps carried gap-fillers and re-extracts on restore when the ' - 'stream is truncated by an extraction error', - () async { - final dense = [ - for (var i = 0; i < 4; i++) - File('${tempDir.path}/dense_$i.jpg')..writeAsStringSync('d$i'), - ]; - final clip = _createFileClip( - id: 'a', - videoPath: '${tempDir.path}/a.mp4', - seconds: 4, - ); - fakeStreamManager.sync(clips: [clip], devicePixelRatio: 1); - controllers.single.add([ - for (var i = 0; i < 4; i++) - StripThumbnail( - path: dense[i].path, - timestamp: Duration(milliseconds: 500 + i * 1000), - ), - ]); - await pumpEventQueue(); - - // The clip's file is re-rendered — a restart carries the dense - // frames as gap-fillers while the new extraction streams in. - final rerenderedClip = _createFileClip( - id: 'a', - videoPath: '${tempDir.path}/a_rerendered.mp4', - seconds: 4, - ); - fakeStreamManager.sync(clips: [rerenderedClip], devicePixelRatio: 1); - expect(controllers, hasLength(2)); - - final fresh = File('${tempDir.path}/fresh.jpg') - ..writeAsStringSync('fresh'); - controllers[1].add([ + test('keeps carried gap-fillers and re-extracts on restore when the ' + 'stream is truncated by an extraction error', () async { + final dense = [ + for (var i = 0; i < 4; i++) + File('${tempDir.path}/dense_$i.jpg')..writeAsStringSync('d$i'), + ]; + final clip = _createFileClip( + id: 'a', + videoPath: '${tempDir.path}/a.mp4', + seconds: 4, + ); + fakeStreamManager.sync(clips: [clip], devicePixelRatio: 1); + controllers.single.add([ + for (var i = 0; i < 4; i++) StripThumbnail( - path: fresh.path, - timestamp: const Duration(milliseconds: 500), + path: dense[i].path, + timestamp: Duration(milliseconds: 500 + i * 1000), ), - ]); - await pumpEventQueue(); - expect(fakeStreamManager['a'].value, hasLength(4)); + ]); + await pumpEventQueue(); - // Decoder failure truncates the stream after the sparse batch. - controllers[1].addError(StateError('decoder died')); - await controllers[1].close(); - await pumpEventQueue(); + // The clip's file is re-rendered — a restart carries the dense + // frames as gap-fillers while the new extraction streams in. + final rerenderedClip = _createFileClip( + id: 'a', + videoPath: '${tempDir.path}/a_rerendered.mp4', + seconds: 4, + ); + fakeStreamManager.sync(clips: [rerenderedClip], devicePixelRatio: 1); + expect(controllers, hasLength(2)); + + final fresh = File('${tempDir.path}/fresh.jpg') + ..writeAsStringSync('fresh'); + controllers[1].add([ + StripThumbnail( + path: fresh.path, + timestamp: const Duration(milliseconds: 500), + ), + ]); + await pumpEventQueue(); + expect(fakeStreamManager['a'].value, hasLength(4)); - // The carried gap-fillers survive — the strip must not collapse - // to the single fresh frame — and their files stay on disk. - final afterError = fakeStreamManager['a'].value; - expect(afterError, hasLength(4)); - expect(afterError.map((t) => t.path), contains(fresh.path)); - for (final file in dense.skip(1)) { - expect(file.existsSync(), isTrue); - } + // Decoder failure truncates the stream after the sparse batch. + controllers[1].addError(StateError('decoder died')); + await controllers[1].close(); + await pumpEventQueue(); - // Remove and restore: the strip was truncated, not complete, so - // a fresh subscription starts to fill the missing frames. - fakeStreamManager.sync(clips: [], devicePixelRatio: 1); - fakeStreamManager.sync(clips: [rerenderedClip], devicePixelRatio: 1); - expect(fakeStreamManager['a'].value, hasLength(4)); - expect(controllers, hasLength(3)); - }, - ); + // The carried gap-fillers survive — the strip must not collapse + // to the single fresh frame — and their files stay on disk. + final afterError = fakeStreamManager['a'].value; + expect(afterError, hasLength(4)); + expect(afterError.map((t) => t.path), contains(fresh.path)); + for (final file in dense.skip(1)) { + expect(file.existsSync(), isTrue); + } + + // Remove and restore: the strip was truncated, not complete, so + // a fresh subscription starts to fill the missing frames. + fakeStreamManager.sync(clips: [], devicePixelRatio: 1); + fakeStreamManager.sync(clips: [rerenderedClip], devicePixelRatio: 1); + expect(fakeStreamManager['a'].value, hasLength(4)); + expect(controllers, hasLength(3)); + }); }); group('retired strips', () { @@ -927,97 +912,85 @@ void main() { File writeFrame(String name) => File('${tempDir.path}/$name.jpg')..writeAsStringSync(name); - test( - 'restores a complete strip instantly without re-extraction when the ' - 'clip id returns with the same file (undo)', - () async { - final frame = writeFrame('a_frame'); - final clipA = _createFileClip( - id: 'a', - videoPath: '${tempDir.path}/a.mp4', - ); - - fakeStreamManager.sync(clips: [clipA], devicePixelRatio: 1); - controllers.single.add([ - StripThumbnail( - path: frame.path, - timestamp: const Duration(seconds: 1), - ), - ]); - await pumpEventQueue(); - // Stream completes — the strip is at full density. - await controllers.single.close(); - await pumpEventQueue(); + test('restores a complete strip instantly without re-extraction when the ' + 'clip id returns with the same file (undo)', () async { + final frame = writeFrame('a_frame'); + final clipA = _createFileClip( + id: 'a', + videoPath: '${tempDir.path}/a.mp4', + ); - // Clip 'a' is replaced (e.g. by split halves) — its strip retires. - final clipB = _createFileClip( - id: 'b', - videoPath: '${tempDir.path}/b.mp4', - ); - fakeStreamManager.sync(clips: [clipB], devicePixelRatio: 1); - expect(frame.existsSync(), isTrue); + fakeStreamManager.sync(clips: [clipA], devicePixelRatio: 1); + controllers.single.add([ + StripThumbnail( + path: frame.path, + timestamp: const Duration(seconds: 1), + ), + ]); + await pumpEventQueue(); + // Stream completes — the strip is at full density. + await controllers.single.close(); + await pumpEventQueue(); - // Undo: 'a' returns with the same file. The strip is restored - // instantly and — being complete — no new subscription starts. - fakeStreamManager.sync( - clips: [clipA, clipB], - devicePixelRatio: 1, - ); - expect( - fakeStreamManager['a'].value.single.path, - equals(frame.path), - ); - expect(controllers, hasLength(2)); + // Clip 'a' is replaced (e.g. by split halves) — its strip retires. + final clipB = _createFileClip( + id: 'b', + videoPath: '${tempDir.path}/b.mp4', + ); + fakeStreamManager.sync(clips: [clipB], devicePixelRatio: 1); + expect(frame.existsSync(), isTrue); + + // Undo: 'a' returns with the same file. The strip is restored + // instantly and — being complete — no new subscription starts. + fakeStreamManager.sync(clips: [clipA, clipB], devicePixelRatio: 1); + expect(fakeStreamManager['a'].value.single.path, equals(frame.path)); + expect(controllers, hasLength(2)); + + // Subsequent syncs must not re-subscribe either. + fakeStreamManager.sync(clips: [clipA, clipB], devicePixelRatio: 1); + expect(controllers, hasLength(2)); + }); - // Subsequent syncs must not re-subscribe either. - fakeStreamManager.sync(clips: [clipA, clipB], devicePixelRatio: 1); - expect(controllers, hasLength(2)); - }, - ); + test('re-extracts a restored complete strip when the clip returns with a ' + 'wider trim than its frames were generated for', () async { + final frame = writeFrame('widened_frame'); + final trimmed = _createFileClip( + id: 'a', + videoPath: '${tempDir.path}/a.mp4', + seconds: 60, + trimStart: const Duration(seconds: 20), + trimEnd: const Duration(milliseconds: 33700), + ); - test( - 're-extracts a restored complete strip when the clip returns with a ' - 'wider trim than its frames were generated for', - () async { - final frame = writeFrame('widened_frame'); - final trimmed = _createFileClip( - id: 'a', - videoPath: '${tempDir.path}/a.mp4', - seconds: 60, - trimStart: const Duration(seconds: 20), - trimEnd: const Duration(milliseconds: 33700), - ); + fakeStreamManager.sync(clips: [trimmed], devicePixelRatio: 1); + controllers.single.add([ + StripThumbnail( + path: frame.path, + timestamp: const Duration(seconds: 22), + ), + ]); + await pumpEventQueue(); + await controllers.single.close(); + await pumpEventQueue(); - fakeStreamManager.sync(clips: [trimmed], devicePixelRatio: 1); - controllers.single.add([ - StripThumbnail( - path: frame.path, - timestamp: const Duration(seconds: 22), + // 'a' leaves the timeline and returns with the trim dragged wide + // open. Its retired frames only cover the old window, so being + // "complete" is not enough to skip re-extraction. + fakeStreamManager.sync(clips: [], devicePixelRatio: 1); + fakeStreamManager.sync( + clips: [ + _createFileClip( + id: 'a', + videoPath: '${tempDir.path}/a.mp4', + seconds: 60, ), - ]); - await pumpEventQueue(); - await controllers.single.close(); - await pumpEventQueue(); - - // 'a' leaves the timeline and returns with the trim dragged wide - // open. Its retired frames only cover the old window, so being - // "complete" is not enough to skip re-extraction. - fakeStreamManager.sync(clips: [], devicePixelRatio: 1); - fakeStreamManager.sync( - clips: [ - _createFileClip( - id: 'a', - videoPath: '${tempDir.path}/a.mp4', - seconds: 60, - ), - ], - devicePixelRatio: 1, - ); + ], + devicePixelRatio: 1, + ); - expect(controllers, hasLength(2)); - expect(fakeStreamManager['a'].value.single.path, equals(frame.path)); - }, - ); + expect(controllers, hasLength(2)); + expect(fakeStreamManager['a'].value.single.path, equals(frame.path)); + }); test( 'restores a partial strip and starts a gap-filling subscription', @@ -1042,10 +1015,7 @@ void main() { fakeStreamManager.sync(clips: [clipA], devicePixelRatio: 1); // Frames restored instantly, plus a fresh subscription for gaps. - expect( - fakeStreamManager['a'].value.single.path, - equals(frame.path), - ); + expect(fakeStreamManager['a'].value.single.path, equals(frame.path)); expect(controllers, hasLength(2)); }, ); @@ -1085,34 +1055,31 @@ void main() { }, ); - test( - 'evicts the oldest retired strip beyond the cap and deletes its ' - 'unreferenced files', - () async { - // Retire 9 strips in sequence — one more than the cap of 8. - final frames = []; - for (var i = 0; i < 9; i++) { - final frame = writeFrame('evict_$i'); - frames.add(frame); - final clip = _createFileClip( - id: 'clip_$i', - videoPath: '${tempDir.path}/clip_$i.mp4', - ); - fakeStreamManager.sync(clips: [clip], devicePixelRatio: 1); - fakeStreamManager['clip_$i'].value = [ - StripThumbnail(path: frame.path, timestamp: Duration.zero), - ]; - } - // Retire the 9th strip too. - fakeStreamManager.sync(clips: [], devicePixelRatio: 1); + test('evicts the oldest retired strip beyond the cap and deletes its ' + 'unreferenced files', () async { + // Retire 9 strips in sequence — one more than the cap of 8. + final frames = []; + for (var i = 0; i < 9; i++) { + final frame = writeFrame('evict_$i'); + frames.add(frame); + final clip = _createFileClip( + id: 'clip_$i', + videoPath: '${tempDir.path}/clip_$i.mp4', + ); + fakeStreamManager.sync(clips: [clip], devicePixelRatio: 1); + fakeStreamManager['clip_$i'].value = [ + StripThumbnail(path: frame.path, timestamp: Duration.zero), + ]; + } + // Retire the 9th strip too. + fakeStreamManager.sync(clips: [], devicePixelRatio: 1); - // The oldest strip fell off the FIFO; the newest 8 survive. - expect(frames.first.existsSync(), isFalse); - for (final frame in frames.skip(1)) { - expect(frame.existsSync(), isTrue); - } - }, - ); + // The oldest strip fell off the FIFO; the newest 8 survive. + expect(frames.first.existsSync(), isFalse); + for (final frame in frames.skip(1)) { + expect(frame.existsSync(), isTrue); + } + }); }); group('pauseAll / resumeAll', () { @@ -1137,16 +1104,15 @@ void main() { onResume: () => resumes++, ); fakeStreamManager = ClipThumbnailManager( - stripThumbnailStreamFactory: - ({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset = Duration.zero, - List? priorityTimestamps, - }) => streamController.stream, + stripThumbnailStreamFactory: ({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset = Duration.zero, + List? priorityTimestamps, + }) => streamController.stream, ); }); @@ -1258,49 +1224,43 @@ void main() { localManager.dispose(); }); - test( - 'deletes a borrowed seed file once when two notifiers share it', - () { - final tempDir = Directory.systemTemp.createTempSync( - 'clip_thumbnail_dispose_test_', - ); - addTearDown(() { - if (tempDir.existsSync()) tempDir.deleteSync(recursive: true); - }); - final borrowed = File('${tempDir.path}/borrowed.jpg') - ..writeAsStringSync('borrowed'); + test('deletes a borrowed seed file once when two notifiers share it', () { + final tempDir = Directory.systemTemp.createTempSync( + 'clip_thumbnail_dispose_test_', + ); + addTearDown(() { + if (tempDir.existsSync()) tempDir.deleteSync(recursive: true); + }); + final borrowed = File('${tempDir.path}/borrowed.jpg') + ..writeAsStringSync('borrowed'); - final localManager = ClipThumbnailManager(); - localManager.sync( - clips: [ - _createFileClip(id: 'src', videoPath: '${tempDir.path}/src.mp4'), - ], - devicePixelRatio: 1, - ); - localManager['src'].value = [ - StripThumbnail(path: borrowed.path, timestamp: Duration.zero), - ]; - // The start half borrows the same source frame file, so both - // notifiers reference it — dispose must delete it once and - // swallow the second (already-gone) deleteSync. - localManager.seedFromSource( - sourceClipId: 'src', - targetClipId: 'start', - sourceRange: const DurationRange( - start: Duration.zero, - end: Duration(seconds: 1), - ), - currentSourcePath: '${tempDir.path}/src.mp4', - ); - expect( - localManager['start'].value.single.path, - equals(borrowed.path), - ); + final localManager = ClipThumbnailManager(); + localManager.sync( + clips: [ + _createFileClip(id: 'src', videoPath: '${tempDir.path}/src.mp4'), + ], + devicePixelRatio: 1, + ); + localManager['src'].value = [ + StripThumbnail(path: borrowed.path, timestamp: Duration.zero), + ]; + // The start half borrows the same source frame file, so both + // notifiers reference it — dispose must delete it once and + // swallow the second (already-gone) deleteSync. + localManager.seedFromSource( + sourceClipId: 'src', + targetClipId: 'start', + sourceRange: const DurationRange( + start: Duration.zero, + end: Duration(seconds: 1), + ), + currentSourcePath: '${tempDir.path}/src.mp4', + ); + expect(localManager['start'].value.single.path, equals(borrowed.path)); - expect(localManager.dispose, returnsNormally); - expect(borrowed.existsSync(), isFalse); - }, - ); + expect(localManager.dispose, returnsNormally); + expect(borrowed.existsSync(), isFalse); + }); }); // ========================================================= @@ -1444,10 +1404,7 @@ void main() { expect(requests, hasLength(2)); expect(requests.last.startOffset, equals(const Duration(seconds: 39))); - expect( - requests.last.end, - equals(const Duration(milliseconds: 47300)), - ); + expect(requests.last.end, equals(const Duration(milliseconds: 47300))); }); test( @@ -1473,10 +1430,7 @@ void main() { // The restart must not blank the strip — the already-extracted // frame stays on screen until fresh batches cover its slot. expect(requests, hasLength(2)); - expect( - windowManager['import'].value.single.path, - equals(frame.path), - ); + expect(windowManager['import'].value.single.path, equals(frame.path)); expect(frame.existsSync(), isTrue); }, ); diff --git a/mobile/test/services/video_editor/stop_motion_render_service_test.dart b/mobile/test/services/video_editor/stop_motion_render_service_test.dart index c74cac8271..66c382e466 100644 --- a/mobile/test/services/video_editor/stop_motion_render_service_test.dart +++ b/mobile/test/services/video_editor/stop_motion_render_service_test.dart @@ -226,13 +226,12 @@ void main() { test( 'keeps the single-pass duration when already >= min output', () async { - StopMotionRenderService.assembleOverride = - ({ - required frames, - required aspectRatio, - frameRate = StopMotionRenderService.defaultFrameRate, - String? taskId, - }) async => '/rendered.mp4'; + StopMotionRenderService.assembleOverride = ({ + required frames, + required aspectRatio, + frameRate = StopMotionRenderService.defaultFrameRate, + String? taskId, + }) async => '/rendered.mp4'; final clip = DivineVideoClip( id: 'sm-long', @@ -258,13 +257,12 @@ void main() { test( 'uses the probed encoded mp4 duration over the frame-hold estimate', () async { - StopMotionRenderService.assembleOverride = - ({ - required frames, - required aspectRatio, - frameRate = StopMotionRenderService.defaultFrameRate, - String? taskId, - }) async => '/rendered.mp4'; + StopMotionRenderService.assembleOverride = ({ + required frames, + required aspectRatio, + frameRate = StopMotionRenderService.defaultFrameRate, + String? taskId, + }) async => '/rendered.mp4'; // The real encoder lands a hair short of the 1332ms hold estimate; // the muxed-audio clamp keys off the clip duration, so the probed // length must win or audio outlives the video (the iOS freeze). @@ -281,13 +279,12 @@ void main() { ); test('falls back to the estimate when the probe returns null', () async { - StopMotionRenderService.assembleOverride = - ({ - required frames, - required aspectRatio, - frameRate = StopMotionRenderService.defaultFrameRate, - String? taskId, - }) async => '/rendered.mp4'; + StopMotionRenderService.assembleOverride = ({ + required frames, + required aspectRatio, + frameRate = StopMotionRenderService.defaultFrameRate, + String? taskId, + }) async => '/rendered.mp4'; StopMotionRenderService.probeDurationOverride = (_) async => null; final result = await StopMotionRenderService.materialize( @@ -299,13 +296,12 @@ void main() { }); test('returns null when the render fails', () async { - StopMotionRenderService.assembleOverride = - ({ - required frames, - required aspectRatio, - frameRate = StopMotionRenderService.defaultFrameRate, - String? taskId, - }) async => null; + StopMotionRenderService.assembleOverride = ({ + required frames, + required aspectRatio, + frameRate = StopMotionRenderService.defaultFrameRate, + String? taskId, + }) async => null; final result = await StopMotionRenderService.materialize( stopMotionClip(), diff --git a/mobile/test/services/video_editor/video_editor_clip_library_save_service_test.dart b/mobile/test/services/video_editor/video_editor_clip_library_save_service_test.dart index 10c753da32..7faf654eee 100644 --- a/mobile/test/services/video_editor/video_editor_clip_library_save_service_test.dart +++ b/mobile/test/services/video_editor/video_editor_clip_library_save_service_test.dart @@ -97,15 +97,14 @@ void main() { ); test('bakes the edits in rather than carrying them on the clip', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/divine_1.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/divine_1.mp4'; final result = await VideoEditorClipLibrarySaveService.flattenClipForLibrary( @@ -134,15 +133,14 @@ void main() { test( 'sizes the library clip to the trimmed section, not the source', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/divine_1.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/divine_1.mp4'; // A 10s source trimmed to its middle 5s. final result = @@ -160,15 +158,14 @@ void main() { ); test('accounts for playback speed in the saved duration', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/divine_1.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/divine_1.mp4'; // 8s of source at 2x plays back in 4s, which is what lands on disk. final result = @@ -214,15 +211,14 @@ void main() { }); test('keeps the source thumbnail when extraction yields nothing', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/divine_1.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/divine_1.mp4'; // The rendered file does not exist here, so extraction returns null — // the library card must still get a frame rather than none. @@ -340,15 +336,14 @@ void main() { ); test('returns null when the render fails or is cancelled', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => null; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => null; final result = await VideoEditorClipLibrarySaveService.flattenClipForLibrary( @@ -362,15 +357,14 @@ void main() { test( 'carries the imported-source attribution onto the saved clip', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/divine_1.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/divine_1.mp4'; final result = await VideoEditorClipLibrarySaveService.flattenClipForLibrary( @@ -398,15 +392,14 @@ void main() { ); test('carries every credit of a merged clip onto the saved clip', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/divine_1.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/divine_1.mp4'; final result = await VideoEditorClipLibrarySaveService.flattenClipForLibrary( diff --git a/mobile/test/services/video_editor/video_editor_merge_service_test.dart b/mobile/test/services/video_editor/video_editor_merge_service_test.dart index d969906747..a75ef6eb58 100644 --- a/mobile/test/services/video_editor/video_editor_merge_service_test.dart +++ b/mobile/test/services/video_editor/video_editor_merge_service_test.dart @@ -136,15 +136,14 @@ void main() { }); test('accounts for playback speed when summing duration', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/merged.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/merged.mp4'; // 4s at 2x = 2s playback; 2s at 1x = 2s → 4s total. final result = await VideoEditorMergeService.mergeClips( @@ -163,15 +162,14 @@ void main() { }); test('returns null when the render fails or is cancelled', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => null; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => null; final result = await VideoEditorMergeService.mergeClips( clips: [ @@ -187,15 +185,14 @@ void main() { test( 'preserves provenance when every clip shares the same source', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/merged.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/merged.mp4'; DivineVideoClip sameSourceClip(String id) => _createClip(id: id).copyWith( @@ -227,15 +224,14 @@ void main() { test( 'preserves provenance union when clips come from different sources', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => '/documents/merged.mp4'; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => '/documents/merged.mp4'; final result = await VideoEditorMergeService.mergeClips( clips: [ diff --git a/mobile/test/services/video_editor/video_editor_render_service_progress_test.dart b/mobile/test/services/video_editor/video_editor_render_service_progress_test.dart index 63d0730bf1..2e4b2e99a5 100644 --- a/mobile/test/services/video_editor/video_editor_render_service_progress_test.dart +++ b/mobile/test/services/video_editor/video_editor_render_service_progress_test.dart @@ -348,16 +348,15 @@ void main() { return '${Directory.systemTemp.path}/rendered-sm.mp4'; }; - NativeProofModeService.proofFileOverride = - ( - File videoFile, { - required bool enableAdvancedCawgEmbedding, - creatorBindingAssertion, - cawgIdentityAssertion, - verifiedIdentityBundle, - clips, - editorStateHistory, - }) async => const model.NativeProofData(videoHash: 'proof'); + NativeProofModeService.proofFileOverride = ( + File videoFile, { + required bool enableAdvancedCawgEmbedding, + creatorBindingAssertion, + cawgIdentityAssertion, + verifiedIdentityBundle, + clips, + editorStateHistory, + }) async => const model.NativeProofData(videoHash: 'proof'); final stopMotionClip = DivineVideoClip( id: 'sm-clip', diff --git a/mobile/test/services/video_editor/video_editor_render_service_test.dart b/mobile/test/services/video_editor/video_editor_render_service_test.dart index ff92af0267..56cae87fba 100644 --- a/mobile/test/services/video_editor/video_editor_render_service_test.dart +++ b/mobile/test/services/video_editor/video_editor_render_service_test.dart @@ -651,13 +651,12 @@ void main() { test('renderVideoToClip maps a cancelled stop-motion assembly to canceled, ' 'not stop_motion_assembly', () async { - StopMotionRenderService.assembleOverride = - ({ - required frames, - required aspectRatio, - frameRate = StopMotionRenderService.defaultFrameRate, - String? taskId, - }) async => throw const RenderCanceledException(); + StopMotionRenderService.assembleOverride = ({ + required frames, + required aspectRatio, + frameRate = StopMotionRenderService.defaultFrameRate, + String? taskId, + }) async => throw const RenderCanceledException(); final stopMotionClip = DivineVideoClip( id: 'sm-clip', @@ -692,15 +691,14 @@ void main() { test('renderVideo keeps returning null so callers that only need the ' 'path are unaffected', () async { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => null; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => null; expect( await VideoEditorRenderService.renderVideo( @@ -747,15 +745,14 @@ void main() { }); void failRenderWith(Object error) { - VideoEditorRenderService.renderVideoOverride = - ({ - required clips, - required usePersistentStorage, - aspectRatio, - parameters, - taskId, - maxOutputDuration, - }) async => throw error; + VideoEditorRenderService.renderVideoOverride = ({ + required clips, + required usePersistentStorage, + aspectRatio, + parameters, + taskId, + maxOutputDuration, + }) async => throw error; } Future exportClip() => expectLater( diff --git a/mobile/test/services/video_event_publisher_audio_degrade_test.dart b/mobile/test/services/video_event_publisher_audio_degrade_test.dart index e5353ad57f..49615f6c5f 100644 --- a/mobile/test/services/video_event_publisher_audio_degrade_test.dart +++ b/mobile/test/services/video_event_publisher_audio_degrade_test.dart @@ -235,8 +235,7 @@ void main() { audioFilePath: audioPath, duration: 6, fileSize: 12345, - sha256Hash: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + sha256Hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', mimeType: 'audio/m4a', ), ); @@ -253,8 +252,7 @@ void main() { success: true, url: 'https://cdn.example.com/audio.m4a', fallbackUrl: 'https://cdn.example.com/audio.m4a', - videoId: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + videoId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), ); @@ -286,67 +284,68 @@ void main() { }, ); - test('an authoritative reusable-audio restriction is not degraded', () async { - const audioPath = '/tmp/divine-audio.m4a'; - when( - () => mockAudioExtractionService.extractAudio( - videoPath: any(named: 'videoPath'), - ), - ).thenAnswer( - (_) async => const AudioExtractionResult( - audioFilePath: audioPath, - duration: 6, - fileSize: 12345, - sha256Hash: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - mimeType: 'audio/m4a', - ), - ); - when( - () => mockAudioExtractionService.cleanupAudioFile(audioPath), - ).thenAnswer((_) async {}); - when( - () => mockBlossomUploadService.uploadAudio( - audioFile: any(named: 'audioFile'), - mimeType: 'audio/m4a', - ), - ).thenAnswer( - (_) async => const BlossomUploadResult( - success: true, - url: 'https://cdn.example.com/audio.m4a', - fallbackUrl: 'https://cdn.example.com/audio.m4a', - videoId: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - ), - ); - stubSigning(); - when( - () => mockNostrClient.publishEventAwaitOk( - any(), - timeout: any(named: 'timeout'), - ), - ).thenAnswer((invocation) async { - final event = invocation.positionalArguments.first as Event; - return PublishOutcome( - eventId: event.id, - acceptedBy: const [], - rejectedBy: const { - 'wss://relay.divine.video': 'blocked: pubkey is suspended', - }, - noResponseFrom: const [], + test( + 'an authoritative reusable-audio restriction is not degraded', + () async { + const audioPath = '/tmp/divine-audio.m4a'; + when( + () => mockAudioExtractionService.extractAudio( + videoPath: any(named: 'videoPath'), + ), + ).thenAnswer( + (_) async => const AudioExtractionResult( + audioFilePath: audioPath, + duration: 6, + fileSize: 12345, + sha256Hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + mimeType: 'audio/m4a', + ), ); - }); - - await expectLater( - publisher.publishDirectUpload(createUpload(), allowAudioReuse: true), - throwsA( - isA().having( - (error) => error.source, - 'source', - AccountRestrictionSource.webSocket, + when( + () => mockAudioExtractionService.cleanupAudioFile(audioPath), + ).thenAnswer((_) async {}); + when( + () => mockBlossomUploadService.uploadAudio( + audioFile: any(named: 'audioFile'), + mimeType: 'audio/m4a', ), - ), - ); - }); + ).thenAnswer( + (_) async => const BlossomUploadResult( + success: true, + url: 'https://cdn.example.com/audio.m4a', + fallbackUrl: 'https://cdn.example.com/audio.m4a', + videoId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + ), + ); + stubSigning(); + when( + () => mockNostrClient.publishEventAwaitOk( + any(), + timeout: any(named: 'timeout'), + ), + ).thenAnswer((invocation) async { + final event = invocation.positionalArguments.first as Event; + return PublishOutcome( + eventId: event.id, + acceptedBy: const [], + rejectedBy: const { + 'wss://relay.divine.video': 'blocked: pubkey is suspended', + }, + noResponseFrom: const [], + ); + }); + + await expectLater( + publisher.publishDirectUpload(createUpload(), allowAudioReuse: true), + throwsA( + isA().having( + (error) => error.source, + 'source', + AccountRestrictionSource.webSocket, + ), + ), + ); + }, + ); }); } diff --git a/mobile/test/services/video_event_publisher_cawg_identity_test.dart b/mobile/test/services/video_event_publisher_cawg_identity_test.dart index cfd428ac7e..4269a4867a 100644 --- a/mobile/test/services/video_event_publisher_cawg_identity_test.dart +++ b/mobile/test/services/video_event_publisher_cawg_identity_test.dart @@ -86,8 +86,7 @@ void main() { final content = invocation.namedArguments[#content] as String; capturedEvent = Event.fromJson({ 'id': 'event123', - 'pubkey': - '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', + 'pubkey': '385c3a6ec0b9d57a4330dbd6284989be5bd00e41c535f9ca39b6ae7c521b81cd', 'created_at': DateTime.now().millisecondsSinceEpoch ~/ 1000, 'kind': 34236, 'tags': tags, diff --git a/mobile/test/services/video_event_publisher_inspired_by_tags_test.dart b/mobile/test/services/video_event_publisher_inspired_by_tags_test.dart index 73c92ce0e3..222d441b56 100644 --- a/mobile/test/services/video_event_publisher_inspired_by_tags_test.dart +++ b/mobile/test/services/video_event_publisher_inspired_by_tags_test.dart @@ -540,8 +540,7 @@ void main() { test('does not emit inspired-by p-tags on a video reply', () async { stubSignAndPublish(); const replyContext = VideoReplyContext( - rootEventId: - 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + rootEventId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', rootEventKind: 34236, rootAuthorPubkey: rootAuthorPubkey, ); diff --git a/mobile/test/services/video_event_publisher_native_proof_test.dart b/mobile/test/services/video_event_publisher_native_proof_test.dart index aeadf04572..73245d6599 100644 --- a/mobile/test/services/video_event_publisher_native_proof_test.dart +++ b/mobile/test/services/video_event_publisher_native_proof_test.dart @@ -96,8 +96,7 @@ void main() { sensorDataCsv: 'timestamp,lat,lon\n2025-01-01,40.7,-74.0', pgpSignature: '-----BEGIN PGP SIGNATURE-----\ntest\n-----END PGP SIGNATURE-----', - publicKey: - '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----', + publicKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----', deviceAttestation: 'attestation_token_12345', timestamp: '2025-01-01T12:00:00Z', ); diff --git a/mobile/test/services/video_event_publisher_publish_confirmation_test.dart b/mobile/test/services/video_event_publisher_publish_confirmation_test.dart index 9607f6b494..69705dc448 100644 --- a/mobile/test/services/video_event_publisher_publish_confirmation_test.dart +++ b/mobile/test/services/video_event_publisher_publish_confirmation_test.dart @@ -1,4 +1,5 @@ import 'dart:io'; + import 'package:blossom_upload_service/blossom_upload_service.dart'; import 'package:creator_sync/creator_sync.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -455,8 +456,7 @@ void main() { audioFilePath: audioPath, duration: 6, fileSize: 12345, - sha256Hash: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + sha256Hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', mimeType: 'audio/m4a', ), ); diff --git a/mobile/test/services/video_event_publisher_test.dart b/mobile/test/services/video_event_publisher_test.dart index ccd0552b16..df94c41150 100644 --- a/mobile/test/services/video_event_publisher_test.dart +++ b/mobile/test/services/video_event_publisher_test.dart @@ -590,10 +590,8 @@ void main() { final result = await publisher.publishVideoEvent( upload: createUpload().copyWith( - streamingMp4Url: - 'https://stream.divine.video/fa4a90a3-6a30-4dc6-9b9d-3f78551c9053/play_360p.mp4', - streamingHlsUrl: - 'https://stream.divine.video/fa4a90a3-6a30-4dc6-9b9d-3f78551c9053/playlist.m3u8', + streamingMp4Url: 'https://stream.divine.video/fa4a90a3-6a30-4dc6-9b9d-3f78551c9053/play_360p.mp4', + streamingHlsUrl: 'https://stream.divine.video/fa4a90a3-6a30-4dc6-9b9d-3f78551c9053/playlist.m3u8', fallbackUrl: 'https://media.divine.video/fa4a90a3.mp4', cdnUrl: 'https://stream.divine.video/legacy/playlist.m3u8', ), @@ -879,8 +877,7 @@ void main() { audioFilePath: audioPath, duration: 6, fileSize: 12345, - sha256Hash: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + sha256Hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', mimeType: 'audio/m4a', ), ); @@ -897,8 +894,7 @@ void main() { success: true, url: 'https://cdn.example.com/audio.m4a', fallbackUrl: 'https://cdn.example.com/audio.m4a', - videoId: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + videoId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), ); when( @@ -1393,8 +1389,7 @@ void main() { success: true, url: 'https://cdn.example/audiohash', fallbackUrl: 'https://cdn.example/audiohash', - videoId: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + videoId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), ); @@ -1503,8 +1498,7 @@ void main() { success: true, url: 'https://cdn.example/audiohash', fallbackUrl: 'https://cdn.example/audiohash', - videoId: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + videoId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), ); diff --git a/mobile/test/services/video_event_service_consolidation_test.dart b/mobile/test/services/video_event_service_consolidation_test.dart index f634dca07e..02c4cfc920 100644 --- a/mobile/test/services/video_event_service_consolidation_test.dart +++ b/mobile/test/services/video_event_service_consolidation_test.dart @@ -268,72 +268,73 @@ void main() { expect( video.reposterPubkey, 'bob', - reason: - 'Backward compatibility: reposterPubkey should work for single reposter', + reason: 'Backward compatibility: reposterPubkey should work for single reposter', ); // Verify: reposterPubkeys (plural) should contain bob expect(video.reposterPubkeys, contains('bob')); }); - test('reposterPubkey is set when original video exists and repost arrives', () { - // This test verifies the fix for the bug where the repost header would - // disappear. When original video is in the list first and a repost - // arrives, consolidation must set BOTH reposterPubkey (singular) and - // reposterPubkeys (plural) for the UI header to display correctly. + test( + 'reposterPubkey is set when original video exists and repost arrives', + () { + // This test verifies the fix for the bug where the repost header would + // disappear. When original video is in the list first and a repost + // arrives, consolidation must set BOTH reposterPubkey (singular) and + // reposterPubkeys (plural) for the UI header to display correctly. - // Add original video (non-repost) first - final originalVideo = TestVideoEventBuilder.create( - id: 'original999', - pubkey: 'alice', - videoUrl: 'https://example.com/video.mp4', - title: 'Original Video', - ); - service.addVideoEventForTesting( - originalVideo, - SubscriptionType.discovery, - isHistorical: false, - ); + // Add original video (non-repost) first + final originalVideo = TestVideoEventBuilder.create( + id: 'original999', + pubkey: 'alice', + videoUrl: 'https://example.com/video.mp4', + title: 'Original Video', + ); + service.addVideoEventForTesting( + originalVideo, + SubscriptionType.discovery, + isHistorical: false, + ); - // Verify: Original video should NOT be marked as repost - var videos = service.getVideos(SubscriptionType.discovery); - expect(videos.length, 1); - expect(videos.first.isRepost, false); - expect(videos.first.reposterPubkey, isNull); + // Verify: Original video should NOT be marked as repost + var videos = service.getVideos(SubscriptionType.discovery); + expect(videos.length, 1); + expect(videos.first.isRepost, false); + expect(videos.first.reposterPubkey, isNull); - // Now a repost arrives for the same video - final bobRepost = VideoEvent.createRepostEvent( - originalEvent: originalVideo, - repostEventId: 'repost999', - reposterPubkey: 'bob', - repostedAt: DateTime.now(), - ); - service.addVideoEventForTesting( - bobRepost, - SubscriptionType.discovery, - isHistorical: false, - ); + // Now a repost arrives for the same video + final bobRepost = VideoEvent.createRepostEvent( + originalEvent: originalVideo, + repostEventId: 'repost999', + reposterPubkey: 'bob', + repostedAt: DateTime.now(), + ); + service.addVideoEventForTesting( + bobRepost, + SubscriptionType.discovery, + isHistorical: false, + ); - // Verify: Should still have only ONE video (consolidated) - videos = service.getVideos(SubscriptionType.discovery); - expect(videos.length, 1); + // Verify: Should still have only ONE video (consolidated) + videos = service.getVideos(SubscriptionType.discovery); + expect(videos.length, 1); - // Verify: The consolidated video should be marked as repost - final consolidatedVideo = videos.first; - expect(consolidatedVideo.isRepost, true); + // Verify: The consolidated video should be marked as repost + final consolidatedVideo = videos.first; + expect(consolidatedVideo.isRepost, true); - // CRITICAL: reposterPubkey (singular) must be set for UI header - // This was the bug - UI checks: video.isRepost && video.reposterPubkey != null - expect( - consolidatedVideo.reposterPubkey, - 'bob', - reason: - 'reposterPubkey (singular) must be set during consolidation for UI header', - ); + // CRITICAL: reposterPubkey (singular) must be set for UI header + // This was the bug - UI checks: video.isRepost && video.reposterPubkey != null + expect( + consolidatedVideo.reposterPubkey, + 'bob', + reason: 'reposterPubkey (singular) must be set during consolidation for UI header', + ); - // Verify: reposterPubkeys (plural) should also be set - expect(consolidatedVideo.reposterPubkeys, isNotNull); - expect(consolidatedVideo.reposterPubkeys, contains('bob')); - }); + // Verify: reposterPubkeys (plural) should also be set + expect(consolidatedVideo.reposterPubkeys, isNotNull); + expect(consolidatedVideo.reposterPubkeys, contains('bob')); + }, + ); }); } diff --git a/mobile/test/services/video_event_service_content_filter_test.dart b/mobile/test/services/video_event_service_content_filter_test.dart index dbaac281cb..d1f050cee0 100644 --- a/mobile/test/services/video_event_service_content_filter_test.dart +++ b/mobile/test/services/video_event_service_content_filter_test.dart @@ -267,8 +267,7 @@ void main() { ); final result = videoEventService.getFilterAction( _FakeLabelEvent( - pubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + pubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', tags: const [ ['content-warning', 'profanity'], ], @@ -282,8 +281,7 @@ void main() { test('relay ingest still hides an owner violence label', () { final result = videoEventService.getFilterAction( _FakeLabelEvent( - pubkey: - '1111111111111111111111111111111111111111111111111111111111111111', + pubkey: '1111111111111111111111111111111111111111111111111111111111111111', tags: const [ ['content-warning', 'violence'], ], diff --git a/mobile/test/services/video_event_service_eose_empty_test.dart b/mobile/test/services/video_event_service_eose_empty_test.dart index d505c90830..ce946d4a6c 100644 --- a/mobile/test/services/video_event_service_eose_empty_test.dart +++ b/mobile/test/services/video_event_service_eose_empty_test.dart @@ -267,48 +267,52 @@ void main() { }, ); - test('logs expected empty profile state without subscription error', () async { - void Function()? capturedOnEose; - final controller = StreamController(); - addTearDown(controller.close); + test( + 'logs expected empty profile state without subscription error', + () async { + void Function()? capturedOnEose; + final controller = StreamController(); + addTearDown(controller.close); - when( - () => mockNostrService.subscribe(any(), onEose: any(named: 'onEose')), - ).thenAnswer((invocation) { - capturedOnEose = invocation.namedArguments[#onEose] as void Function()?; - return controller.stream; - }); + when( + () => mockNostrService.subscribe(any(), onEose: any(named: 'onEose')), + ).thenAnswer((invocation) { + capturedOnEose = + invocation.namedArguments[#onEose] as void Function()?; + return controller.stream; + }); - await videoEventService.subscribeToVideoFeed( - subscriptionType: SubscriptionType.profile, - authors: const [_profileAuthor], - ); + await videoEventService.subscribeToVideoFeed( + subscriptionType: SubscriptionType.profile, + authors: const [_profileAuthor], + ); - capturedOnEose!(); - await pumpEventQueue(); - - final logs = LogCaptureService().getRecentLogs(); - expect( - logs.where( - (entry) => - entry.level == LogLevel.error && - entry.message.contains( - 'subscription filtering is too restrictive OR subscription stream is broken', - ), - ), - isEmpty, - ); - expect( - logs.where( - (entry) => - entry.level == LogLevel.info && - entry.message.contains( - 'No cached events match the empty SubscriptionType.profile subscription filters', - ), - ), - isNotEmpty, - ); - }); + capturedOnEose!(); + await pumpEventQueue(); + + final logs = LogCaptureService().getRecentLogs(); + expect( + logs.where( + (entry) => + entry.level == LogLevel.error && + entry.message.contains( + 'subscription filtering is too restrictive OR subscription stream is broken', + ), + ), + isEmpty, + ); + expect( + logs.where( + (entry) => + entry.level == LogLevel.info && + entry.message.contains( + 'No cached events match the empty SubscriptionType.profile subscription filters', + ), + ), + isNotEmpty, + ); + }, + ); test( 'keeps subscription error when filtered diagnostic query has events', diff --git a/mobile/test/services/video_event_service_like_count_addressable_test.dart b/mobile/test/services/video_event_service_like_count_addressable_test.dart index 6798ab603c..f3f90c6b7a 100644 --- a/mobile/test/services/video_event_service_like_count_addressable_test.dart +++ b/mobile/test/services/video_event_service_like_count_addressable_test.dart @@ -94,8 +94,7 @@ void main() { expect( addressableIds, isNotNull, - reason: - 'addressableIds should be non-null for a batch containing a kind 34236 video', + reason: 'addressableIds should be non-null for a batch containing a kind 34236 video', ); final targetVideo = VideoEvent.fromNostrEvent(targetEvent); @@ -108,8 +107,7 @@ void main() { expect( addressableIds[targetVideo.id], equals(targetVideo.addressableId), - reason: - 'The addressable ID should be the kind:pubkey:d-tag string from the video', + reason: 'The addressable ID should be the kind:pubkey:d-tag string from the video', ); }, ); diff --git a/mobile/test/services/video_event_service_sorted_queries_integration_test.dart b/mobile/test/services/video_event_service_sorted_queries_integration_test.dart index af8243887e..5775c71272 100644 --- a/mobile/test/services/video_event_service_sorted_queries_integration_test.dart +++ b/mobile/test/services/video_event_service_sorted_queries_integration_test.dart @@ -273,8 +273,7 @@ void main() { expect( filterJson.containsKey('sort'), false, - reason: - 'Should fall back to standard filter when relay does not support divine extensions', + reason: 'Should fall back to standard filter when relay does not support divine extensions', ); expect(filterJson['limit'], 50); }, diff --git a/mobile/test/services/video_event_service_with_event_router_test.dart b/mobile/test/services/video_event_service_with_event_router_test.dart index d4e95c95ab..b147682994 100644 --- a/mobile/test/services/video_event_service_with_event_router_test.dart +++ b/mobile/test/services/video_event_service_with_event_router_test.dart @@ -144,8 +144,7 @@ void main() { ); videoEvent.id = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; - videoEvent.sig = - 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'; + videoEvent.sig = 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'; // Subscribe to discovery feed await videoEventService.subscribeToVideoFeed( @@ -184,8 +183,7 @@ void main() { ); profileEvent.id = 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; - profileEvent.sig = - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + profileEvent.sig = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; // Subscribe to discovery feed await videoEventService.subscribeToVideoFeed( @@ -236,8 +234,7 @@ void main() { ); contactsEvent.id = '2222222222222222222222222222222222222222222222222222222222222222'; - contactsEvent.sig = - '3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333'; + contactsEvent.sig = '3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333'; // Subscribe to discovery feed await videoEventService.subscribeToVideoFeed( @@ -274,8 +271,7 @@ void main() { ); reactionEvent.id = '5555555555555555555555555555555555555555555555555555555555555555'; - reactionEvent.sig = - '6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666'; + reactionEvent.sig = '6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666'; // Subscribe to discovery feed await videoEventService.subscribeToVideoFeed( @@ -307,8 +303,7 @@ void main() { ); unknownEvent.id = '8888888888888888888888888888888888888888888888888888888888888888'; - unknownEvent.sig = - '9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'; + unknownEvent.sig = '9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'; // Subscribe to discovery feed await videoEventService.subscribeToVideoFeed( diff --git a/mobile/test/services/video_kind_migration_test.dart b/mobile/test/services/video_kind_migration_test.dart index 38dac834b4..15a835aeca 100644 --- a/mobile/test/services/video_kind_migration_test.dart +++ b/mobile/test/services/video_kind_migration_test.dart @@ -53,8 +53,7 @@ void main() { expect( NIP71VideoKinds.getAllVideoKinds(), isNot(contains(deprecatedKind)), - reason: - 'Kind 32222 is deprecated and should not be in accepted video kinds', + reason: 'Kind 32222 is deprecated and should not be in accepted video kinds', ); }); diff --git a/mobile/test/services/video_publish/video_publish_service_test.dart b/mobile/test/services/video_publish/video_publish_service_test.dart index 398b0c2ae2..68f5213dcd 100644 --- a/mobile/test/services/video_publish/video_publish_service_test.dart +++ b/mobile/test/services/video_publish/video_publish_service_test.dart @@ -165,9 +165,10 @@ void main() { collaboratorInviteService: mockCollaboratorInviteService, mentionResolutionService: mockMentionResolutionService, performanceMonitor: fakePerformanceMonitor, - onProgressChanged: - ({required double progress, required String draftId}) => - progressChanges.add(progress), + onProgressChanged: ({ + required double progress, + required String draftId, + }) => progressChanges.add(progress), ); }); @@ -615,9 +616,10 @@ void main() { collaboratorInviteService: mockCollaboratorInviteService, mentionResolutionService: mockMentionResolutionService, subtitlePublishTimeout: const Duration(milliseconds: 20), - onProgressChanged: - ({required double progress, required String draftId}) => - progressChanges.add(progress), + onProgressChanged: ({ + required double progress, + required String draftId, + }) => progressChanges.add(progress), ); final result = await boundedService.publishVideo( @@ -684,9 +686,10 @@ void main() { collaboratorInviteService: mockCollaboratorInviteService, mentionResolutionService: mockMentionResolutionService, subtitlePublishTimeout: const Duration(milliseconds: 20), - onProgressChanged: - ({required double progress, required String draftId}) => - progressChanges.add(progress), + onProgressChanged: ({ + required double progress, + required String draftId, + }) => progressChanges.add(progress), ); final result = await boundedService.publishVideo( @@ -2756,8 +2759,10 @@ void main() { mentionResolutionService: mockMentionResolutionService, performanceMonitor: fakePerformanceMonitor, languagePreferenceService: languageService, - onProgressChanged: - ({required double progress, required String draftId}) {}, + onProgressChanged: ({ + required double progress, + required String draftId, + }) {}, ); test('omits the language tag when the user never declared one', () async { diff --git a/mobile/test/services/web_iframe_nostr_bridge_test.dart b/mobile/test/services/web_iframe_nostr_bridge_test.dart index 15fb9de5d5..18c3b6ad2c 100644 --- a/mobile/test/services/web_iframe_nostr_bridge_test.dart +++ b/mobile/test/services/web_iframe_nostr_bridge_test.dart @@ -161,8 +161,7 @@ void main() { final signed = { ...unsigned, 'pubkey': fakePubkey, - 'id': - 'bbbb22222222222222222222222222222222222222222222222222222222bbbb', + 'id': 'bbbb22222222222222222222222222222222222222222222222222222222bbbb', 'sig': 'cafe' * 32, }; when(() => signer.signEvent(any())).thenAnswer((_) async => signed); diff --git a/mobile/test/services/zendesk_support_service_test.dart b/mobile/test/services/zendesk_support_service_test.dart index d259fb3aef..f62bf29a54 100644 --- a/mobile/test/services/zendesk_support_service_test.dart +++ b/mobile/test/services/zendesk_support_service_test.dart @@ -852,8 +852,7 @@ void main() { if (call.method == 'createTicket') { throw PlatformException( code: 'UPLOAD_FAILED', - message: - 'File not found: /private/var/mobile/Containers/Data/Application/foo.jpg', + message: 'File not found: /private/var/mobile/Containers/Data/Application/foo.jpg', ); } return null; diff --git a/mobile/test/startup/main_startup_registration_test.dart b/mobile/test/startup/main_startup_registration_test.dart index 3b217281eb..29e32c9491 100644 --- a/mobile/test/startup/main_startup_registration_test.dart +++ b/mobile/test/startup/main_startup_registration_test.dart @@ -79,8 +79,10 @@ void main() { updaterCreations++; return shared ??= _FakeUpdater(); }, - updateSubscribedTrack: - ({required updater, required preferences}) async {}, + updateSubscribedTrack: ({ + required updater, + required preferences, + }) async {}, ); // The synchronous FFI probe in the real constructor is the cost of moving diff --git a/mobile/test/state/state_model_compatibility_test.dart b/mobile/test/state/state_model_compatibility_test.dart new file mode 100644 index 0000000000..cdbc0c8ee3 --- /dev/null +++ b/mobile/test/state/state_model_compatibility_test.dart @@ -0,0 +1,184 @@ +// ABOUTME: Pins the Freezed-era copyWith and collection contracts of app state. +// ABOUTME: Prevents handwritten Equatable models from weakening immutability. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:models/models.dart'; +import 'package:openvine/state/curation_state.dart'; +import 'package:openvine/state/seen_videos_state.dart'; +import 'package:openvine/state/user_profile_state.dart'; +import 'package:openvine/state/video_feed_state.dart'; + +void main() { + group('nullable copyWith arguments', () { + test('preserve existing values when omitted', () { + final timestamp = DateTime(2026, 9, 8); + final state = VideoFeedState( + videos: const [], + hasMoreContent: false, + error: 'stale error', + lastUpdated: timestamp, + totalVideoCount: 4, + ); + + final copied = state.copyWith(isRefreshing: true); + + expect(copied.error, 'stale error'); + expect(copied.lastUpdated, timestamp); + expect(copied.totalVideoCount, 4); + }); + + test('clear existing values when explicitly null', () { + final state = VideoFeedState( + videos: const [], + hasMoreContent: false, + error: 'stale error', + lastUpdated: DateTime(2026, 9, 8), + totalVideoCount: 4, + ); + + final copied = state.copyWith( + error: null, + lastUpdated: null, + totalVideoCount: null, + ); + + expect(copied.error, isNull); + expect(copied.lastUpdated, isNull); + expect(copied.totalVideoCount, isNull); + }); + + test('reject values of the wrong nullable-field type', () { + const state = VideoFeedState( + videos: [], + hasMoreContent: false, + error: 'stale error', + ); + + expect(() => state.copyWith(error: 42), throwsA(isA())); + }); + }); + + group('state collections', () { + // Every collection here is built through a mutable local rather than a + // `const` literal on purpose: a const collection already throws on + // `clear()`, so a const fixture would keep these tests green even if the + // getters stopped wrapping at all. + test('remain deeply comparable across distinct collection instances', () { + expect( + SeenVideosState(seenVideoIds: mutableSet(['one'])), + SeenVideosState(seenVideoIds: mutableSet(['one'])), + ); + }); + + test('curation lists remain unmodifiable', () { + final state = CurationState( + editorsPicks: mutableList([]), + isLoading: false, + trending: mutableList([]), + curationSets: mutableList([]), + ); + + expect(state.editorsPicks.clear, throwsUnsupportedError); + expect(state.trending.clear, throwsUnsupportedError); + expect(state.curationSets.clear, throwsUnsupportedError); + }); + + test('seen-video sets remain unmodifiable', () { + final state = SeenVideosState(seenVideoIds: mutableSet(['one'])); + + expect(state.seenVideoIds.clear, throwsUnsupportedError); + }); + + test('profile cache collections remain unmodifiable', () { + final state = UserProfileState( + pendingRequests: mutableSet(['one']), + knownMissingProfiles: mutableSet(['two']), + missingProfileRetryAfter: mutableMap({'two': DateTime(2026, 9, 8)}), + pendingBatchPubkeys: mutableSet(['three']), + ); + + expect(state.pendingRequests.clear, throwsUnsupportedError); + expect(state.knownMissingProfiles.clear, throwsUnsupportedError); + expect(state.missingProfileRetryAfter.clear, throwsUnsupportedError); + expect(state.pendingBatchPubkeys.clear, throwsUnsupportedError); + }); + + test('video-feed collections remain unmodifiable', () { + final state = VideoFeedState( + videos: mutableList([]), + hasMoreContent: false, + videoListSources: mutableMap({ + 'video': mutableSet(['list']), + }), + listOnlyVideoIds: mutableSet(['video']), + ); + + expect(state.videos.clear, throwsUnsupportedError); + expect(state.videoListSources.clear, throwsUnsupportedError); + expect(state.listOnlyVideoIds.clear, throwsUnsupportedError); + }); + }); + + group('collection getter equality', () { + // Freezed handed out views that compare equal when they wrap the same + // source, which is what lets `fooProvider.select((s) => s.items)` skip a + // rebuild. A plain `dart:collection` view has identity `==`, so a getter + // allocating one per read never compares equal to itself and every + // selector over it fires on every unrelated state change. + test('holds across two reads of the same field', () { + final state = VideoFeedState( + videos: mutableList([]), + hasMoreContent: false, + videoListSources: mutableMap({ + 'video': mutableSet(['list']), + }), + listOnlyVideoIds: mutableSet(['video']), + ); + + expect(state.videos == state.videos, isTrue); + expect(state.videoListSources == state.videoListSources, isTrue); + expect(state.listOnlyVideoIds == state.listOnlyVideoIds, isTrue); + }); + + test('survives a copyWith that does not touch the collection', () { + final state = CurationState( + editorsPicks: mutableList([]), + isLoading: false, + trending: mutableList([]), + ); + + final copied = state.copyWith(isLoading: true); + + expect(copied.editorsPicks == state.editorsPicks, isTrue); + expect(copied.trending == state.trending, isTrue); + }); + + test('stays flat instead of nesting one view per copyWith', () { + var state = SeenVideosState(seenVideoIds: mutableSet(['one'])); + final first = state.seenVideoIds; + + for (var i = 0; i < 5; i++) { + state = state.copyWith(isInitialized: true); + } + + expect(state.seenVideoIds == first, isTrue); + }); + + test('breaks when the collection is replaced', () { + final state = SeenVideosState(seenVideoIds: mutableSet(['one'])); + + final copied = state.copyWith(seenVideoIds: mutableSet(['one', 'two'])); + + expect(copied.seenVideoIds == state.seenVideoIds, isFalse); + }); + }); +} + +/// A growable list the analyzer cannot fold into a `const` literal. +List mutableList(Iterable items) => List.of(items); + +/// A mutable set the analyzer cannot fold into a `const` literal. +Set mutableSet(Iterable items) => Set.of(items); + +/// A mutable map the analyzer cannot fold into a `const` literal. +Map mutableMap(Map items) => Map.of(items); diff --git a/mobile/test/tools/app_icon_set_test.dart b/mobile/test/tools/app_icon_set_test.dart index 700c298e04..ff74d8a4fc 100644 --- a/mobile/test/tools/app_icon_set_test.dart +++ b/mobile/test/tools/app_icon_set_test.dart @@ -423,9 +423,9 @@ void main() { }); test('$_webManifestPath carries the configured theme colour', () { - final manifest = - jsonDecode(File(_webManifestPath).readAsStringSync()) - as Map; + final manifest = jsonDecode( + File(_webManifestPath).readAsStringSync(), + ) as Map; expect( manifest['theme_color'], _brandGreenHex, diff --git a/mobile/test/tools/apple_required_reason_probe_test.dart b/mobile/test/tools/apple_required_reason_probe_test.dart index 315bd424dc..4ba4bb225d 100644 --- a/mobile/test/tools/apple_required_reason_probe_test.dart +++ b/mobile/test/tools/apple_required_reason_probe_test.dart @@ -10,13 +10,11 @@ void main() { final probe = File( 'scripts/lib/apple_required_reason_probe.py', ).absolute.path; - final catalogue = - jsonDecode( - File( - 'scripts/data/apple_required_reason_catalogue.json', - ).readAsStringSync(), - ) - as Map; + final catalogue = jsonDecode( + File( + 'scripts/data/apple_required_reason_catalogue.json', + ).readAsStringSync(), + ) as Map; Map payloadForCatalogue() { final references = {}; diff --git a/mobile/test/tools/dependency_provenance_detector_test.dart b/mobile/test/tools/dependency_provenance_detector_test.dart index 3e4bd02755..73f569f8c8 100644 --- a/mobile/test/tools/dependency_provenance_detector_test.dart +++ b/mobile/test/tools/dependency_provenance_detector_test.dart @@ -124,9 +124,11 @@ packages: expect(run().exitCode, 0); }); - test('flags a pinned git dependency repointed to a different repository', () { - const sha = '847d0c0183c9c09946ce321c107decd3fd9f56b2'; - writeLock(''' + test( + 'flags a pinned git dependency repointed to a different repository', + () { + const sha = '847d0c0183c9c09946ce321c107decd3fd9f56b2'; + writeLock(''' packages: c2pa_flutter: dependency: "direct main" @@ -138,9 +140,9 @@ packages: source: git version: "0.0.3" '''); - expect(run(update: true).exitCode, 0); + expect(run(update: true).exitCode, 0); - writeLock(''' + writeLock(''' packages: c2pa_flutter: dependency: "direct main" @@ -153,16 +155,17 @@ packages: version: "0.0.3" '''); - final res = run(); + final res = run(); - expect(res.exitCode, 1, reason: outputOf(res)); - expect( - res.stdout, - contains( - 'git:c2pa_flutter:https://github.com/attacker/c2pa-flutter-fork.git:PINNED', - ), - ); - }); + expect(res.exitCode, 1, reason: outputOf(res)); + expect( + res.stdout, + contains( + 'git:c2pa_flutter:https://github.com/attacker/c2pa-flutter-fork.git:PINNED', + ), + ); + }, + ); test('a movable ref cannot be silenced by baselining it', () { // The allowlist sanctions WHICH deps may be non-hosted, never that a ref diff --git a/mobile/test/tools/launch_image_set_test.dart b/mobile/test/tools/launch_image_set_test.dart index 42ca5bed80..8ef505acac 100644 --- a/mobile/test/tools/launch_image_set_test.dart +++ b/mobile/test/tools/launch_image_set_test.dart @@ -242,9 +242,9 @@ void main() { test( 'sizes every scale from one ${_iosWidthPt}x${_iosHeightPt}pt box', () async { - final contents = - jsonDecode(File('$_imagesetDir/Contents.json').readAsStringSync()) - as Map; + final contents = jsonDecode( + File('$_imagesetDir/Contents.json').readAsStringSync(), + ) as Map; final declared = { for (final image in contents['images']! as List) (image as Map)['filename'] as String: diff --git a/mobile/test/tools/mobile_qa_workflow_contract_test.dart b/mobile/test/tools/mobile_qa_workflow_contract_test.dart index f9b321af02..93d89111bb 100644 --- a/mobile/test/tools/mobile_qa_workflow_contract_test.dart +++ b/mobile/test/tools/mobile_qa_workflow_contract_test.dart @@ -22,18 +22,14 @@ void main() { // new `push:` left every needle matching while the workflow started // firing on every push to every branch. setUpAll(() { - workflow = - loadYaml( - File( - '../.github/workflows/mobile_service_integration_tests.yaml', - ).readAsStringSync(), - ) - as Map; - mobileCiWorkflow = - loadYaml( - File('../.github/workflows/mobile_ci.yaml').readAsStringSync(), - ) - as Map; + workflow = loadYaml( + File( + '../.github/workflows/mobile_service_integration_tests.yaml', + ).readAsStringSync(), + ) as Map; + mobileCiWorkflow = loadYaml( + File('../.github/workflows/mobile_ci.yaml').readAsStringSync(), + ) as Map; triggers = workflow['on'] as Map; changes = (workflow['jobs'] as Map)['changes'] diff --git a/mobile/test/tools/naming_convention_test.dart b/mobile/test/tools/naming_convention_test.dart index af2120936c..585681c8c7 100644 --- a/mobile/test/tools/naming_convention_test.dart +++ b/mobile/test/tools/naming_convention_test.dart @@ -2,6 +2,7 @@ // ABOUTME: Validates that no source file name carries a temporal suffix import 'dart:io'; + import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as path; diff --git a/mobile/test/tools/pre_build_ios_test.dart b/mobile/test/tools/pre_build_ios_test.dart index 0401c1201b..60c74f6ace 100644 --- a/mobile/test/tools/pre_build_ios_test.dart +++ b/mobile/test/tools/pre_build_ios_test.dart @@ -11,10 +11,10 @@ import 'package:path/path.dart' as p; /// The shared Runner scheme runs this script as a pre-action of every Xcode /// build. Any Flutter command it runs there re-injects plugins, which rewrites /// `ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/ -/// Package.swift` at Flutter's default `.iOS("13.0")` floor. Nothing on the +/// Package.swift` at Flutter's default iOS floor. Nothing on the /// Xcode-driven build path raises that floor back to the project's 16.0, so /// the build then fails with "requires minimum platform version 16.0 ... but -/// this target supports 13.0" for every plugin with a higher floor. The +/// this target supports an older version" for every plugin with a higher floor. The /// script must therefore never invoke `flutter`; that property is pinned here. void main() { group('pre_build_ios.sh', () { @@ -99,6 +99,16 @@ void main() { .replaceAll('/opt/homebrew/bin/pod', '/nonexistent/homebrew/pod') .replaceAll('/usr/local/bin/pod', '/nonexistent/local/pod'), ); + final helperSource = File( + p.join( + Directory.current.path, + 'scripts/ensure_ios_swift_package_floor.rb', + ), + ); + final helperDestination = File( + p.join(sandbox.path, 'scripts/ensure_ios_swift_package_floor.rb'), + )..parent.createSync(recursive: true); + helperDestination.writeAsStringSync(helperSource.readAsStringSync()); stubBin = Directory(p.join(sandbox.path, 'bin'))..createSync(); stubLogs = Directory(p.join(sandbox.path, 'logs'))..createSync(); Directory(p.join(sandbox.path, 'home')).createSync(); @@ -110,16 +120,18 @@ void main() { sandbox.deleteSync(recursive: true); }); - test('never invokes flutter, which would reset the Swift package floor ' - 'to iOS 13.0', () { - final now = DateTime.now(); - writeFile('ios/Podfile.lock', now.subtract(const Duration(hours: 1))); - writeFile('ios/Pods/Manifest.lock', now); + test( + 'never invokes flutter, which could reset the Swift package floor', + () { + final now = DateTime.now(); + writeFile('ios/Podfile.lock', now.subtract(const Duration(hours: 1))); + writeFile('ios/Pods/Manifest.lock', now); - expectSuccess(runScript()); + expectSuccess(runScript()); - expect(stubLog('flutter').existsSync(), isFalse); - }); + expect(stubLog('flutter').existsSync(), isFalse); + }, + ); test('skips pod install when Pods/Manifest.lock is as new as ' 'Podfile.lock', () { @@ -156,7 +168,38 @@ void main() { expect(stubLog('pod').readAsStringSync(), contains('install')); }); - test('raises the generated Swift package floor without Flutter', () { + test( + 'raises Flutter 3.47 generated Swift package floor without Flutter', + () { + final now = DateTime.now(); + writeFile('ios/Podfile.lock', now); + writeFile('ios/Pods/Manifest.lock', now); + swiftPackageManifest() + ..parent.createSync(recursive: true) + ..writeAsStringSync( + 'let package = Package(\n' + ' name: "FlutterGeneratedPluginSwiftPackage",\n' + ' platforms: [\n' + ' .iOS("15.0")\n' + ' ]\n' + ')\n', + ); + + expectSuccess(runScript()); + expectSuccess(runScript()); + + expect( + swiftPackageManifest().readAsStringSync(), + contains('.iOS("16.0")'), + ); + expect( + swiftPackageManifest().readAsStringSync(), + isNot(contains('.iOS("15.0")')), + ); + }, + ); + + test('accepts a generated Swift package floor newer than iOS 16', () { final now = DateTime.now(); writeFile('ios/Podfile.lock', now); writeFile('ios/Pods/Manifest.lock', now); @@ -166,21 +209,16 @@ void main() { 'let package = Package(\n' ' name: "FlutterGeneratedPluginSwiftPackage",\n' ' platforms: [\n' - ' .iOS("13.0")\n' + ' .iOS("17.0")\n' ' ]\n' ')\n', ); expectSuccess(runScript()); - expectSuccess(runScript()); expect( swiftPackageManifest().readAsStringSync(), - contains('.iOS("16.0")'), - ); - expect( - swiftPackageManifest().readAsStringSync(), - isNot(contains('.iOS("13.0")')), + contains('.iOS("17.0")'), ); }); diff --git a/mobile/test/tools/service_god_file_ceiling_test.dart b/mobile/test/tools/service_god_file_ceiling_test.dart index 0240de115f..6e4fc31ddf 100644 --- a/mobile/test/tools/service_god_file_ceiling_test.dart +++ b/mobile/test/tools/service_god_file_ceiling_test.dart @@ -44,10 +44,11 @@ void main() { ); } - List baselineRows() => File(baselinePath) - .readAsLinesSync() - .where((l) => l.isNotEmpty && !l.startsWith('#')) - .toList(); + List baselineRows() => + File(baselinePath) + .readAsLinesSync() + .where((l) => l.isNotEmpty && !l.startsWith('#')) + .toList(); setUp(() { tmp = Directory.systemTemp.createTempSync('service_god_file_test'); diff --git a/mobile/test/unit/curated_list_relay_sync_test.dart b/mobile/test/unit/curated_list_relay_sync_test.dart index 9b022f328b..d91c9a449f 100644 --- a/mobile/test/unit/curated_list_relay_sync_test.dart +++ b/mobile/test/unit/curated_list_relay_sync_test.dart @@ -332,8 +332,7 @@ void main() { stubListPublishing( client: freshMockNostrService, auth: freshMockAuthService, - pubkey: - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', ); // Create local list without initializing (to avoid initial relay sync) diff --git a/mobile/test/unit/profile_screen_empty_container_test.dart b/mobile/test/unit/profile_screen_empty_container_test.dart index 35052be1b2..17e47ddf6a 100644 --- a/mobile/test/unit/profile_screen_empty_container_test.dart +++ b/mobile/test/unit/profile_screen_empty_container_test.dart @@ -219,8 +219,7 @@ void main() { Text( 'Failed to load', style: TextStyle( - color: Colors - .white70, // Should match VineTheme.secondaryText + color: Colors.white70, // Should match VineTheme.secondaryText fontSize: 12, ), ), diff --git a/mobile/test/unit/services/video_event_hashtag_bug_test.dart b/mobile/test/unit/services/video_event_hashtag_bug_test.dart index c134dc445e..dbd49b6b42 100644 --- a/mobile/test/unit/services/video_event_hashtag_bug_test.dart +++ b/mobile/test/unit/services/video_event_hashtag_bug_test.dart @@ -67,8 +67,7 @@ void main() { expect( result.length, equals(2), - reason: - 'Current implementation returns duplicates when same video exists in multiple lists', + reason: 'Current implementation returns duplicates when same video exists in multiple lists', ); // Both results are the same video diff --git a/mobile/test/unit/services/video_event_service_force_refresh_test.dart b/mobile/test/unit/services/video_event_service_force_refresh_test.dart index 2b947b660c..86faaf9491 100644 --- a/mobile/test/unit/services/video_event_service_force_refresh_test.dart +++ b/mobile/test/unit/services/video_event_service_force_refresh_test.dart @@ -60,8 +60,7 @@ Event createVideoEvent({ createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, ); event.id = id; - event.sig = - 'sig_aaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff11112222333344445555666677778888'; + event.sig = 'sig_aaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff11112222333344445555666677778888'; event.sources.add('wss://relay.divine.video'); return event; } diff --git a/mobile/test/unit/services/video_event_service_reset_resubscribe_test.dart b/mobile/test/unit/services/video_event_service_reset_resubscribe_test.dart index 00f0cdb579..f38bae8b59 100644 --- a/mobile/test/unit/services/video_event_service_reset_resubscribe_test.dart +++ b/mobile/test/unit/services/video_event_service_reset_resubscribe_test.dart @@ -61,8 +61,7 @@ Event createVideoEvent({ createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, ); event.id = id; - event.sig = - 'sig_1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000aaaabbbbcccc'; + event.sig = 'sig_1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000aaaabbbbcccc'; event.sources.add('wss://relay.divine.video'); return event; } diff --git a/mobile/test/unit/services/video_event_service_subscription_test.dart b/mobile/test/unit/services/video_event_service_subscription_test.dart index 5c051642a9..6c8fb5c8aa 100644 --- a/mobile/test/unit/services/video_event_service_subscription_test.dart +++ b/mobile/test/unit/services/video_event_service_subscription_test.dart @@ -134,8 +134,7 @@ void main() { expect( subscriptionCalls.length, equals(2), - reason: - 'Should allow subscription with different parameters (no authors vs specific authors)', + reason: 'Should allow subscription with different parameters (no authors vs specific authors)', ); // Verify the subscriptions have different filters @@ -336,65 +335,66 @@ void main() { ); }); - test('should handle the classic vines -> open feed sequence correctly', () async { - // This is the exact sequence that's failing in production - final subscriptionCalls = >[]; - when( - () => mockNostrService.subscribe(any(), onEose: any(named: 'onEose')), - ).thenAnswer((invocation) { - final filters = invocation.positionalArguments[0] as List; - subscriptionCalls.add(filters); - return eventStreamController.stream; - }); + test( + 'should handle the classic vines -> open feed sequence correctly', + () async { + // This is the exact sequence that's failing in production + final subscriptionCalls = >[]; + when( + () => mockNostrService.subscribe(any(), onEose: any(named: 'onEose')), + ).thenAnswer((invocation) { + final filters = invocation.positionalArguments[0] as List; + subscriptionCalls.add(filters); + return eventStreamController.stream; + }); - // Step 1: Load classic vines (specific author) - await videoEventService.subscribeToVideoFeed( - subscriptionType: SubscriptionType.discovery, - authors: [ - '25315276cbaeb8f2ed998ed55d15ef8c9cf2027baea191d1253d9a5c69a2b856', - ], - limit: 100, - ); + // Step 1: Load classic vines (specific author) + await videoEventService.subscribeToVideoFeed( + subscriptionType: SubscriptionType.discovery, + authors: [ + '25315276cbaeb8f2ed998ed55d15ef8c9cf2027baea191d1253d9a5c69a2b856', + ], + limit: 100, + ); - expect(subscriptionCalls.length, equals(1)); - expect(subscriptionCalls[0][0].authors, isNotNull); - expect(subscriptionCalls[0][0].authors!.length, equals(1)); + expect(subscriptionCalls.length, equals(1)); + expect(subscriptionCalls[0][0].authors, isNotNull); + expect(subscriptionCalls[0][0].authors!.length, equals(1)); - // Step 2: Load open feed (no author filter) - THIS IS BEING WRONGLY REJECTED - await videoEventService.subscribeToVideoFeed( - subscriptionType: SubscriptionType.discovery, - limit: 300, - replace: false, - ); + // Step 2: Load open feed (no author filter) - THIS IS BEING WRONGLY REJECTED + await videoEventService.subscribeToVideoFeed( + subscriptionType: SubscriptionType.discovery, + limit: 300, + replace: false, + ); - expect( - subscriptionCalls.length, - equals(2), - reason: - 'Open feed subscription should not be rejected as duplicate of author-specific subscription', - ); - expect( - subscriptionCalls[1][0].authors, - isNull, - reason: 'Open feed should have no author filter', - ); + expect( + subscriptionCalls.length, + equals(2), + reason: 'Open feed subscription should not be rejected as duplicate of author-specific subscription', + ); + expect( + subscriptionCalls[1][0].authors, + isNull, + reason: 'Open feed should have no author filter', + ); - // Step 3: Load editor picks (different specific author) - await videoEventService.subscribeToVideoFeed( - subscriptionType: SubscriptionType.discovery, - authors: [ - '70ed6c56d6fb355f102a1e985741b5ee65f6ae9f772e028894b321bc74854082', - ], - limit: 50, - replace: false, - ); + // Step 3: Load editor picks (different specific author) + await videoEventService.subscribeToVideoFeed( + subscriptionType: SubscriptionType.discovery, + authors: [ + '70ed6c56d6fb355f102a1e985741b5ee65f6ae9f772e028894b321bc74854082', + ], + limit: 50, + replace: false, + ); - expect( - subscriptionCalls.length, - equals(3), - reason: - 'All three subscriptions should be allowed as they have different parameters', - ); - }); + expect( + subscriptionCalls.length, + equals(3), + reason: 'All three subscriptions should be allowed as they have different parameters', + ); + }, + ); }); } diff --git a/mobile/test/unit/subscription_manager_tdd_test.dart b/mobile/test/unit/subscription_manager_tdd_test.dart index ec8c40e6d0..569a025d83 100644 --- a/mobile/test/unit/subscription_manager_tdd_test.dart +++ b/mobile/test/unit/subscription_manager_tdd_test.dart @@ -155,8 +155,7 @@ void main() { expect( receivedEvents.length, equals(2), - reason: - 'SubscriptionManager should forward events from stream to callback - THIS WILL FAIL FIRST (TDD Red phase)', + reason: 'SubscriptionManager should forward events from stream to callback - THIS WILL FAIL FIRST (TDD Red phase)', ); }, ); @@ -231,8 +230,7 @@ void main() { expect( receivedEvents.length, equals(2), - reason: - 'Direct stream should receive events (proves test setup is correct)', + reason: 'Direct stream should receive events (proves test setup is correct)', ); }, ); diff --git a/mobile/test/utils/device_memory_util_test.dart b/mobile/test/utils/device_memory_util_test.dart index ff08b6d270..8f95e1ba63 100644 --- a/mobile/test/utils/device_memory_util_test.dart +++ b/mobile/test/utils/device_memory_util_test.dart @@ -2,6 +2,7 @@ // ABOUTME: Tests memory tier detection and resolution scaling logic import 'dart:ui' show Size; + import 'package:flutter_test/flutter_test.dart'; import 'package:openvine/utils/device_memory_util.dart'; diff --git a/mobile/test/utils/proofmode_publishing_helpers_test.dart b/mobile/test/utils/proofmode_publishing_helpers_test.dart index 255d1660ca..56e5cc60a9 100644 --- a/mobile/test/utils/proofmode_publishing_helpers_test.dart +++ b/mobile/test/utils/proofmode_publishing_helpers_test.dart @@ -2,6 +2,7 @@ // ABOUTME: Tests verification level detection and Nostr tag creation from NativeProofData import 'dart:convert'; + import 'package:flutter_test/flutter_test.dart'; import 'package:models/models.dart' show NativeProofData; import 'package:openvine/utils/proofmode_publishing_helpers.dart'; diff --git a/mobile/test/widgets/add_to_list_dialog_test.dart b/mobile/test/widgets/add_to_list_dialog_test.dart index 294941655f..269be91e64 100644 --- a/mobile/test/widgets/add_to_list_dialog_test.dart +++ b/mobile/test/widgets/add_to_list_dialog_test.dart @@ -71,8 +71,7 @@ void main() { _fakeLists = [ CuratedList( id: 'list0123456789abcdef0123456789abcdef0123456789abcdef0123456789', - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'My Test List', description: 'A test list', videoEventIds: const [], @@ -93,8 +92,7 @@ void main() { _fakeLists = [ CuratedList( id: 'list0123456789abcdef0123456789abcdef0123456789abcdef0123456789', - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'Contains Video', videoEventIds: const [ '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', @@ -114,8 +112,7 @@ void main() { _fakeLists = [ CuratedList( id: 'list0123456789abcdef0123456789abcdef0123456789abcdef0123456789', - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'Empty List', videoEventIds: const [], createdAt: DateTime.now(), @@ -133,8 +130,7 @@ void main() { _fakeLists = [ CuratedList( id: 'list0123456789abcdef0123456789abcdef0123456789abcdef0123456789', - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'Three Videos', videoEventIds: const ['vid1', 'vid2', 'vid3'], createdAt: DateTime.now(), @@ -156,8 +152,7 @@ void main() { _fakeLists = [ CuratedList( id: listId, - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'My List', videoEventIds: const [], createdAt: DateTime.now(), @@ -189,8 +184,7 @@ void main() { _fakeLists = [ CuratedList( id: listId, - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'My List', videoEventIds: [testVideo.id], createdAt: DateTime.now(), @@ -224,8 +218,7 @@ void main() { _fakeLists = [ CuratedList( id: listId, - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'My List', isPublic: false, // Enough references that one more cannot fit in a single NIP-44 @@ -261,8 +254,7 @@ void main() { _fakeLists = [ CuratedList( id: listId, - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'My List', videoEventIds: const [], createdAt: DateTime.now(), @@ -297,8 +289,7 @@ void main() { _fakeLists = [ CuratedList( id: 'list_a_23456789abcdef0123456789abcdef0123456789abcdef0123456789', - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'Favorites', videoEventIds: const [], createdAt: DateTime.now(), @@ -306,8 +297,7 @@ void main() { ), CuratedList( id: 'list_b_23456789abcdef0123456789abcdef0123456789abcdef0123456789', - pubkey: - 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', + pubkey: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789', name: 'Watch Later', isPublic: false, videoEventIds: const [], diff --git a/mobile/test/widgets/clickable_hashtag_text_test.dart b/mobile/test/widgets/clickable_hashtag_text_test.dart index f90eb5a569..920ba89848 100644 --- a/mobile/test/widgets/clickable_hashtag_text_test.dart +++ b/mobile/test/widgets/clickable_hashtag_text_test.dart @@ -431,8 +431,7 @@ void main() { name: mention, rawData: const {'name': mention}, createdAt: DateTime.utc(2026, 6, 21), - eventId: - 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', + eventId: 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', ); final profileRepository = _MockProfileRepository(); when( diff --git a/mobile/test/widgets/composable_video_grid_test.dart b/mobile/test/widgets/composable_video_grid_test.dart index f3a644c2f8..bf716566f0 100644 --- a/mobile/test/widgets/composable_video_grid_test.dart +++ b/mobile/test/widgets/composable_video_grid_test.dart @@ -113,8 +113,7 @@ void main() { testVideos = [ VideoEvent( id: 'video1', - pubkey: - 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', // 64-char hex pubkey + pubkey: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', // 64-char hex pubkey content: 'Test video 1', title: 'Video 1', videoUrl: 'https://example.com/video1.mp4', @@ -127,8 +126,7 @@ void main() { ), VideoEvent( id: 'video2', - pubkey: - 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', // 64-char hex pubkey + pubkey: 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', // 64-char hex pubkey content: 'Test video 2', title: 'Video 2', videoUrl: 'https://example.com/video2.mp4', @@ -141,8 +139,7 @@ void main() { ), VideoEvent( id: 'broken_video', - pubkey: - 'c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4', // 64-char hex pubkey + pubkey: 'c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4', // 64-char hex pubkey content: 'Broken video', title: 'Broken Video', videoUrl: 'https://example.com/broken.mp4', diff --git a/mobile/test/widgets/library/sounds_tab_test.dart b/mobile/test/widgets/library/sounds_tab_test.dart index 457d6c676f..290a3af55c 100644 --- a/mobile/test/widgets/library/sounds_tab_test.dart +++ b/mobile/test/widgets/library/sounds_tab_test.dart @@ -111,8 +111,7 @@ AudioEvent _sound({ }) { return AudioEvent( id: id, - pubkey: - 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'test_pubkey_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', createdAt: createdAt, title: title, duration: 6, diff --git a/mobile/test/widgets/list_card_test.dart b/mobile/test/widgets/list_card_test.dart index 4df0db7af0..d473545c35 100644 --- a/mobile/test/widgets/list_card_test.dart +++ b/mobile/test/widgets/list_card_test.dart @@ -58,8 +58,7 @@ void main() { await tester.pumpWidget( buildSubject( isPublic: true, - pubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + pubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ), ); diff --git a/mobile/test/widgets/list_search_card_test.dart b/mobile/test/widgets/list_search_card_test.dart index dd0c9e62a8..a0a4c9b58e 100644 --- a/mobile/test/widgets/list_search_card_test.dart +++ b/mobile/test/widgets/list_search_card_test.dart @@ -98,8 +98,7 @@ void main() { displayName: 'Alice', rawData: const {}, createdAt: DateTime(2026), - eventId: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + eventId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', ), ), ), diff --git a/mobile/test/widgets/profile/profile_comments_grid_test.dart b/mobile/test/widgets/profile/profile_comments_grid_test.dart index 7ed9fcaf8b..f9c6585c1d 100644 --- a/mobile/test/widgets/profile/profile_comments_grid_test.dart +++ b/mobile/test/widgets/profile/profile_comments_grid_test.dart @@ -239,8 +239,7 @@ void main() { displayName: 'Alice', rawData: const {}, createdAt: DateTime(2026), - eventId: - 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + eventId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ), ), ), diff --git a/mobile/test/widgets/profile/profile_header_widget_test.dart b/mobile/test/widgets/profile/profile_header_widget_test.dart index 11542d28c8..9761f72da3 100644 --- a/mobile/test/widgets/profile/profile_header_widget_test.dart +++ b/mobile/test/widgets/profile/profile_header_widget_test.dart @@ -522,8 +522,7 @@ void main() { ProfileBadgeViewData( badge: const Nip58ProfileBadgeRef( definitionCoordinate: '30009:$issuerUserHex:daily-diviner', - awardEventId: - '00000000000000000000000000000000000000000000000000000000000000aa', + awardEventId: '00000000000000000000000000000000000000000000000000000000000000aa', ), award: Nip58BadgeAward( event: _badgeAwardEvent(), @@ -858,8 +857,7 @@ void main() { ProfileBadgeViewData( badge: const Nip58ProfileBadgeRef( definitionCoordinate: '30009:$issuerUserHex:daily-diviner', - awardEventId: - '00000000000000000000000000000000000000000000000000000000000000aa', + awardEventId: '00000000000000000000000000000000000000000000000000000000000000aa', ), award: Nip58BadgeAward( event: _badgeAwardEvent(), @@ -919,8 +917,7 @@ void main() { ProfileBadgeViewData( badge: const Nip58ProfileBadgeRef( definitionCoordinate: '30009:$issuerUserHex:daily-diviner', - awardEventId: - '00000000000000000000000000000000000000000000000000000000000000aa', + awardEventId: '00000000000000000000000000000000000000000000000000000000000000aa', ), award: Nip58BadgeAward( event: _badgeAwardEvent(), diff --git a/mobile/test/widgets/report_content_dialog_test.dart b/mobile/test/widgets/report_content_dialog_test.dart index 1d5f802185..bb085bc926 100644 --- a/mobile/test/widgets/report_content_dialog_test.dart +++ b/mobile/test/widgets/report_content_dialog_test.dart @@ -65,8 +65,7 @@ void main() { ); testNostrEvent.id = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2'; - testNostrEvent.sig = - 'aa11bb22cc33dd44ee55ff66aa11bb22cc33dd44ee55ff66aa11bb22cc33dd44ee55ff66aa11bb22cc33dd44ee55ff66aa11bb22cc33dd44ee55ff66aa11bb22'; + testNostrEvent.sig = 'aa11bb22cc33dd44ee55ff66aa11bb22cc33dd44ee55ff66aa11bb22cc33dd44ee55ff66aa11bb22cc33dd44ee55ff66aa11bb22cc33dd44ee55ff66aa11bb22'; testVideo = VideoEvent.fromNostrEvent(testNostrEvent); mockReportingService = _MockContentReportingService(); diff --git a/mobile/test/widgets/share_video_menu_collaborators_test.dart b/mobile/test/widgets/share_video_menu_collaborators_test.dart index f3bae7cc8c..72f45c9c58 100644 --- a/mobile/test/widgets/share_video_menu_collaborators_test.dart +++ b/mobile/test/widgets/share_video_menu_collaborators_test.dart @@ -49,10 +49,8 @@ void main() { verify( () => inviteService.sendInvite( collaboratorPubkey: collaboratorPubkey, - creatorPubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - videoAddress: - '34236:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:video-d-tag', + creatorPubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + videoAddress: '34236:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:video-d-tag', title: 'Updated title', thumbnailUrl: 'https://cdn.example.com/thumb.jpg', relayHint: 'wss://relay.divine.video', diff --git a/mobile/test/widgets/sound_tile_test.dart b/mobile/test/widgets/sound_tile_test.dart index 9a6060655f..c5c46f7c69 100644 --- a/mobile/test/widgets/sound_tile_test.dart +++ b/mobile/test/widgets/sound_tile_test.dart @@ -18,8 +18,7 @@ void main() { setUp(() { testSound = AudioEvent( id: 'test-audio-event-id-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - pubkey: - 'test-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'test-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', createdAt: 1704067200, title: 'Original sound - @testuser', duration: 6.2, @@ -279,8 +278,7 @@ void main() { testWidgets('handles null duration', (tester) async { final nullDurationSound = AudioEvent( id: 'null-duration-id-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - pubkey: - 'null-duration-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef0123', + pubkey: 'null-duration-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef0123', createdAt: 1704067200, title: 'No duration sound', ); @@ -295,14 +293,12 @@ void main() { ) async { final legacyOriginalSound = AudioEvent( id: 'video_legacy-original-id-0123456789abcdef0123456789abcdef0123456789abcdef', - pubkey: - 'legacy-original-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef', + pubkey: 'legacy-original-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef', createdAt: 1704067200, title: 'Original sound - Kenya', url: 'https://example.com/video.mp4', source: 'Original Sound', - sourceVideoReference: - '34236:legacy-original-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef:legacy-vine', + sourceVideoReference: '34236:legacy-original-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef:legacy-vine', ); await tester.pumpWidget(buildTestWidget(sound: legacyOriginalSound)); @@ -324,8 +320,7 @@ void main() { testWidgets('displays fallback for null title', (tester) async { final noTitleSound = AudioEvent( id: 'no-title-id-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - pubkey: - 'no-title-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef01234567', + pubkey: 'no-title-pubkey-0123456789abcdef0123456789abcdef0123456789abcdef01234567', createdAt: 1704067200, duration: 5.0, ); @@ -337,8 +332,7 @@ void main() { testWidgets('truncates long title with ellipsis', (tester) async { final longTitleSound = testSound.copyWith( - title: - 'This is a very long title that should be truncated with ellipsis', + title: 'This is a very long title that should be truncated with ellipsis', ); await tester.pumpWidget(buildTestWidget(sound: longTitleSound)); diff --git a/mobile/test/widgets/subtitle_editor/subtitle_editor_stage_test.dart b/mobile/test/widgets/subtitle_editor/subtitle_editor_stage_test.dart index f73520d9e3..1f8fae5c07 100644 --- a/mobile/test/widgets/subtitle_editor/subtitle_editor_stage_test.dart +++ b/mobile/test/widgets/subtitle_editor/subtitle_editor_stage_test.dart @@ -64,8 +64,7 @@ void main() { playbackUrls: playbackUrls, initializePreviewController: initializePreviewController, loadPreviewSources: loadPreviewSources, - videoId: - '0000000000000000000000000000000000000000000000000000000000000000', + videoId: '0000000000000000000000000000000000000000000000000000000000000000', cues: cues, totalDuration: const Duration(seconds: 8), selectedCue: selectedCue, @@ -187,13 +186,12 @@ void main() { await tester.pumpWidget( pump( cues: const [EditableCue(start: 0, end: 1000, text: 'one')], - loadFrames: - ({ - required String videoUrl, - required String videoId, - required Duration duration, - required double devicePixelRatio, - }) => controller.stream, + loadFrames: ({ + required String videoUrl, + required String videoId, + required Duration duration, + required double devicePixelRatio, + }) => controller.stream, ), ); controller.add([ diff --git a/mobile/test/widgets/upload_failure_listener_test.dart b/mobile/test/widgets/upload_failure_listener_test.dart index 4f45e8a3c4..fd72de0354 100644 --- a/mobile/test/widgets/upload_failure_listener_test.dart +++ b/mobile/test/widgets/upload_failure_listener_test.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:typed_data'; + import 'package:analytics/analytics.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:flutter/material.dart'; diff --git a/mobile/test/widgets/user_picker_sheet_test.dart b/mobile/test/widgets/user_picker_sheet_test.dart index a9393faf1e..fcc866a8a5 100644 --- a/mobile/test/widgets/user_picker_sheet_test.dart +++ b/mobile/test/widgets/user_picker_sheet_test.dart @@ -437,8 +437,7 @@ void main() { eventId: 'viewer-event', ); final mutual = UserProfile( - pubkey: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + pubkey: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', name: 'Mutual', rawData: const {'name': 'Mutual'}, createdAt: DateTime.now(), @@ -536,8 +535,7 @@ void main() { eventId: 'viewer-event', ); final mutual = UserProfile( - pubkey: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + pubkey: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', name: 'Mutual', rawData: const {'name': 'Mutual'}, createdAt: DateTime.now(), @@ -1343,8 +1341,7 @@ void main() { eventId: 'viewer-event', ); final other = UserProfile( - pubkey: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + pubkey: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', name: 'Other', rawData: const {'name': 'Other'}, createdAt: DateTime.now(), diff --git a/mobile/test/widgets/user_profile_tile_feature_flag_test.dart b/mobile/test/widgets/user_profile_tile_feature_flag_test.dart index de94e1629e..3872498582 100644 --- a/mobile/test/widgets/user_profile_tile_feature_flag_test.dart +++ b/mobile/test/widgets/user_profile_tile_feature_flag_test.dart @@ -128,8 +128,7 @@ Widget _buildSubject({ ], child: Scaffold( body: UserProfileTile( - pubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + pubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', onTap: onTap, ), ), diff --git a/mobile/test/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip_test.dart b/mobile/test/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip_test.dart index 0b9116ef5c..b0ceb17c2d 100644 --- a/mobile/test/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip_test.dart +++ b/mobile/test/widgets/video_editor/timeline_editor/strips/video_editor_timeline_clip_strip_test.dart @@ -179,9 +179,7 @@ void main() { TimelineConstants.clipWaveformBarWidth, baseline, ), - const Radius.circular( - TimelineConstants.clipWaveformBarWidth / 2, - ), + const Radius.circular(TimelineConstants.clipWaveformBarWidth / 2), ), ), ); @@ -290,14 +288,11 @@ void main() { // 3 s of content at 100 px/s → 300 px wide. sourceStartOffset 1.3 s // puts the recording-anchored slot grid 130 px in, leaving a 34 px // phase (130 % 48) that shifts the frames but not the band. - final clip = - _createTestClip( - id: 'clip1', - seconds: 3, - trimStartMs: 500, - ).copyWith( - sourceStartOffset: const Duration(milliseconds: 1300), - ); + final clip = _createTestClip( + id: 'clip1', + seconds: 3, + trimStartMs: 500, + ).copyWith(sourceStartOffset: const Duration(milliseconds: 1300)); await tester.pumpWidget( buildWidget( @@ -339,16 +334,15 @@ void main() { 'swaps from source-timed preview to rebased rendered geometry', (tester) async { final manager = ClipThumbnailManager( - stripThumbnailStreamFactory: - ({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset = Duration.zero, - List? priorityTimestamps, - }) => const Stream.empty(), + stripThumbnailStreamFactory: ({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset = Duration.zero, + List? priorityTimestamps, + }) => const Stream.empty(), ); addTearDown(manager.dispose); @@ -357,9 +351,10 @@ void main() { const totalWidth = 170.0; // Preview phase: still source-timed against the source video. - final previewEnd = _createTestClip(id: 'end', seconds: 3).copyWith( - trimStart: const Duration(milliseconds: 1300), - ); + final previewEnd = _createTestClip( + id: 'end', + seconds: 3, + ).copyWith(trimStart: const Duration(milliseconds: 1300)); await tester.pumpWidget( buildWidget( clips: [previewEnd], @@ -443,16 +438,15 @@ void main() { 'stays file-anchored', (tester) async { final manager = ClipThumbnailManager( - stripThumbnailStreamFactory: - ({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset = Duration.zero, - List? priorityTimestamps, - }) => const Stream.empty(), + stripThumbnailStreamFactory: ({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset = Duration.zero, + List? priorityTimestamps, + }) => const Stream.empty(), ); addTearDown(manager.dispose); @@ -474,9 +468,10 @@ void main() { ]; // File-anchored reference: identical geometry with no offset. - final plain = _createTestClip(id: 'end', seconds: 3).copyWith( - duration: const Duration(milliseconds: 1700), - ); + final plain = _createTestClip( + id: 'end', + seconds: 3, + ).copyWith(duration: const Duration(milliseconds: 1700)); await tester.pumpWidget( buildWidget( clips: [plain], @@ -1020,16 +1015,15 @@ void main() { setUp(() { clipBloc = _MockClipEditorBloc(); manager = ClipThumbnailManager( - stripThumbnailStreamFactory: - ({ - required String videoPath, - required String clipId, - required Duration duration, - required Size outputSize, - required int thumbsPerSecond, - Duration startOffset = Duration.zero, - List? priorityTimestamps, - }) => const Stream>.empty(), + stripThumbnailStreamFactory: ({ + required String videoPath, + required String clipId, + required Duration duration, + required Size outputSize, + required int thumbsPerSecond, + Duration startOffset = Duration.zero, + List? priorityTimestamps, + }) => const Stream>.empty(), ); }); diff --git a/mobile/test/widgets/video_editor/timeline_editor/video_editor_timeline_interactive_body_test.dart b/mobile/test/widgets/video_editor/timeline_editor/video_editor_timeline_interactive_body_test.dart index c9a38674cb..1778d95f15 100644 --- a/mobile/test/widgets/video_editor/timeline_editor/video_editor_timeline_interactive_body_test.dart +++ b/mobile/test/widgets/video_editor/timeline_editor/video_editor_timeline_interactive_body_test.dart @@ -97,33 +97,32 @@ void main() { onReorder: (_) {}, onReorderChanged: (_) {}, trimmingClipId: null, - onTrimChanged: - ({ - required clipId, - required isStart, - required trimStart, - required trimEnd, - }) {}, + onTrimChanged: ({ + required clipId, + required isStart, + required trimStart, + required trimEnd, + }) {}, onTrimDragChanged: (_) {}, onClipTapped: (_) {}, isMultiSelectMode: false, selectedClipIds: const {}, - onOverlayItemMoved: - ({ - required item, - required startTime, - required row, - required insertAbove, - }) {}, - onOverlayItemMoving: - ({required item, required startTime}) {}, - onOverlayItemTrimmed: - ({ - required item, - required startTime, - required endTime, - required isStart, - }) {}, + onOverlayItemMoved: ({ + required item, + required startTime, + required row, + required insertAbove, + }) {}, + onOverlayItemMoving: ({ + required item, + required startTime, + }) {}, + onOverlayItemTrimmed: ({ + required item, + required startTime, + required endTime, + required isStart, + }) {}, onOverlayTrimDragChanged: (_) {}, onOverlayItemTapped: (_) {}, onOverlayDragStarted: (_) {}, diff --git a/mobile/test/widgets/video_editor/timeline_editor/video_editor_timeline_rules_indicator_test.dart b/mobile/test/widgets/video_editor/timeline_editor/video_editor_timeline_rules_indicator_test.dart index 6917d3a3e9..be75cb3553 100644 --- a/mobile/test/widgets/video_editor/timeline_editor/video_editor_timeline_rules_indicator_test.dart +++ b/mobile/test/widgets/video_editor/timeline_editor/video_editor_timeline_rules_indicator_test.dart @@ -98,36 +98,39 @@ void main() { expect(sizedBox.width, equals(expectedWidth)); }); - testWidgets('keeps the box on the editor axis with an overlap transition', ( - tester, - ) async { - // 2×3s clips with a 1s dissolve: the rendered output is 5s, but the box - // must stay 6s wide so the ruler aligns with the full-length clip strip. - const pps = 100.0; - const dissolve1s = ClipTransition( - type: ClipTransitionType.dissolve, - duration: Duration(seconds: 1), - ); - final clips = [ - clip('a', const Duration(seconds: 3), transition: dissolve1s), - clip('b', const Duration(seconds: 3)), - ]; - - await tester.pumpWidget( - buildWidget( - totalDuration: const Duration(seconds: 6), - pixelsPerSecond: pps, - clips: clips, - ), - ); - - final sizedBox = tester.widget( - find.byWidgetPredicate( - (w) => w is SizedBox && w.height == TimelineConstants.rulerHeight, - ), - ); - expect(sizedBox.width, equals(6.0 * pps)); - }); + testWidgets( + 'keeps the box on the editor axis with an overlap transition', + ( + tester, + ) async { + // 2×3s clips with a 1s dissolve: the rendered output is 5s, but the box + // must stay 6s wide so the ruler aligns with the full-length clip strip. + const pps = 100.0; + const dissolve1s = ClipTransition( + type: ClipTransitionType.dissolve, + duration: Duration(seconds: 1), + ); + final clips = [ + clip('a', const Duration(seconds: 3), transition: dissolve1s), + clip('b', const Duration(seconds: 3)), + ]; + + await tester.pumpWidget( + buildWidget( + totalDuration: const Duration(seconds: 6), + pixelsPerSecond: pps, + clips: clips, + ), + ); + + final sizedBox = tester.widget( + find.byWidgetPredicate( + (w) => w is SizedBox && w.height == TimelineConstants.rulerHeight, + ), + ); + expect(sizedBox.width, equals(6.0 * pps)); + }, + ); }); group('layout', () { diff --git a/mobile/test/widgets/video_feed_item/feed_looping_contract_test.dart b/mobile/test/widgets/video_feed_item/feed_looping_contract_test.dart index 27ecebbc31..f210167ab7 100644 --- a/mobile/test/widgets/video_feed_item/feed_looping_contract_test.dart +++ b/mobile/test/widgets/video_feed_item/feed_looping_contract_test.dart @@ -83,24 +83,27 @@ void main() { } }); - test('the cap clears classic Vine assets and is not the recording limit', () { - // Longest classic Vine measured with ffprobe on media.divine.video (#6421). - // Capping below this would cut the musical loop point off every one of - // them. - const longestClassicVine = Duration(milliseconds: 6533); + test( + 'the cap clears classic Vine assets and is not the recording limit', + () { + // Longest classic Vine measured with ffprobe on media.divine.video (#6421). + // Capping below this would cut the musical loop point off every one of + // them. + const longestClassicVine = Duration(milliseconds: 6533); - expect( - AppConstants.maxFeedPlaybackDuration, - greaterThan(longestClassicVine), - ); - expect( - AppConstants.maxFeedPlaybackDuration, - greaterThan(VideoEditorConstants.maxDuration), - reason: - 'maxDuration is the 6.3s recording limit, not a playback cap; ' - 'reusing it truncates classic Vines.', - ); - }); + expect( + AppConstants.maxFeedPlaybackDuration, + greaterThan(longestClassicVine), + ); + expect( + AppConstants.maxFeedPlaybackDuration, + greaterThan(VideoEditorConstants.maxDuration), + reason: + 'maxDuration is the 6.3s recording limit, not a playback cap; ' + 'reusing it truncates classic Vines.', + ); + }, + ); }); } diff --git a/mobile/test/widgets/video_feed_item/inspired_by_attribution_row_test.dart b/mobile/test/widgets/video_feed_item/inspired_by_attribution_row_test.dart index b17105188c..cd72206c5e 100644 --- a/mobile/test/widgets/video_feed_item/inspired_by_attribution_row_test.dart +++ b/mobile/test/widgets/video_feed_item/inspired_by_attribution_row_test.dart @@ -116,8 +116,7 @@ void main() { ) async { final video = VideoEvent( id: 'video-id', - pubkey: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + pubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', createdAt: 1757385263, content: 'Test video', timestamp: DateTime.fromMillisecondsSinceEpoch(1757385263000), diff --git a/mobile/test/widgets/video_feed_item/paused_video_overlay_test.dart b/mobile/test/widgets/video_feed_item/paused_video_overlay_test.dart index fd303d03b7..b92c9d74fe 100644 --- a/mobile/test/widgets/video_feed_item/paused_video_overlay_test.dart +++ b/mobile/test/widgets/video_feed_item/paused_video_overlay_test.dart @@ -78,8 +78,7 @@ void main() { child: Scaffold( body: PausedVideoOverlay( controller: controller, - videoId: - 'a1b2c3d4e5f6789012345678901234567890abcdef123456789012345678901234', + videoId: 'a1b2c3d4e5f6789012345678901234567890abcdef123456789012345678901234', ), ), ), diff --git a/mobile/test/widgets/video_metadata/modes/edit/video_metadata_edit_stack_test.dart b/mobile/test/widgets/video_metadata/modes/edit/video_metadata_edit_stack_test.dart index 1a10e03476..217c577c11 100644 --- a/mobile/test/widgets/video_metadata/modes/edit/video_metadata_edit_stack_test.dart +++ b/mobile/test/widgets/video_metadata/modes/edit/video_metadata_edit_stack_test.dart @@ -50,8 +50,7 @@ Widget _buildSubject(SharedPreferences preferences) { home: VideoMetadataEditStack( video: VideoEvent( id: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - pubkey: - 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + pubkey: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', createdAt: 1757385263, content: 'Description', timestamp: DateTime.fromMillisecondsSinceEpoch(1757385263 * 1000), diff --git a/mobile/test/widgets/video_metadata/video_metadata_inspired_by_input_test.dart b/mobile/test/widgets/video_metadata/video_metadata_inspired_by_input_test.dart index 8ceabfa48a..6ec5a72bb5 100644 --- a/mobile/test/widgets/video_metadata/video_metadata_inspired_by_input_test.dart +++ b/mobile/test/widgets/video_metadata/video_metadata_inspired_by_input_test.dart @@ -231,8 +231,7 @@ void main() { tester, ) async { final state = VideoEditorProviderState( - inspiredByNpub: - 'npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', + inspiredByNpub: 'npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', ); await tester.pumpWidget( @@ -264,8 +263,7 @@ void main() { tester, ) async { final state = VideoEditorProviderState( - inspiredByNpub: - 'npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', + inspiredByNpub: 'npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', ); await tester.pumpWidget( diff --git a/mobile/test/widgets/video_thumbnail_404_test.dart b/mobile/test/widgets/video_thumbnail_404_test.dart index ba6ae73244..ab7b6c29b7 100644 --- a/mobile/test/widgets/video_thumbnail_404_test.dart +++ b/mobile/test/widgets/video_thumbnail_404_test.dart @@ -20,8 +20,7 @@ void main() { timestamp: DateTime.now(), createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, videoUrl: 'https://example.com/video.mp4', - thumbnailUrl: - 'https://cdn.divine.video/99657957e77a1d27c7c850c3ea35fd2b/thumbnails/thumbnail.jpg', // This is the 404 URL from the crash + thumbnailUrl: 'https://cdn.divine.video/99657957e77a1d27c7c850c3ea35fd2b/thumbnails/thumbnail.jpg', // This is the 404 URL from the crash blurhash: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj', // Valid blurhash for fallback title: 'Test Video', ); diff --git a/mobile/test/widgets/video_thumbnail_aspect_ratio_test.dart b/mobile/test/widgets/video_thumbnail_aspect_ratio_test.dart index 7d5bc4fd77..3c0d899b47 100644 --- a/mobile/test/widgets/video_thumbnail_aspect_ratio_test.dart +++ b/mobile/test/widgets/video_thumbnail_aspect_ratio_test.dart @@ -62,8 +62,7 @@ void main() { expect( aspectRatioWidget.aspectRatio, equals(2 / 3), - reason: - 'Thumbnail aspect ratio should be 2:3 portrait fallback when no dimensions metadata', + reason: 'Thumbnail aspect ratio should be 2:3 portrait fallback when no dimensions metadata', ); }); @@ -305,8 +304,7 @@ void main() { expect( aspectRatioWidget.aspectRatio, equals(1280 / 720), - reason: - 'Thumbnail aspect ratio should match video dimensions (landscape)', + reason: 'Thumbnail aspect ratio should match video dimensions (landscape)', ); }, ); @@ -349,8 +347,7 @@ void main() { expect( aspectRatioWidget.aspectRatio, equals(2 / 3), - reason: - 'Thumbnail should fallback to 2:3 portrait aspect ratio when dimensions are missing', + reason: 'Thumbnail should fallback to 2:3 portrait aspect ratio when dimensions are missing', ); }, ); diff --git a/mobile/test/widgets/video_thumbnail_widget_test.dart b/mobile/test/widgets/video_thumbnail_widget_test.dart index c29bf9972c..bbb03a3783 100644 --- a/mobile/test/widgets/video_thumbnail_widget_test.dart +++ b/mobile/test/widgets/video_thumbnail_widget_test.dart @@ -173,38 +173,40 @@ void main() { expect(find.byType(VineCachedImage), findsOneWidget); }); - testWidgets('renders blurhash without image load for dead media thumbnail', ( - tester, - ) async { - const blurhash = 'LEHV6nWB2yk8pyo0adR*.7kCMdnj'; - final videoWithDeadThumbnail = createTestVideoEvent( - id: 'test-dead-thumbnail', - thumbnailUrl: - 'https://stream.divine.video/fa4a90a3-6a30-4dc6-9b9d-3f78551c9053/thumbnail.jpg', - blurhash: blurhash, - ); + testWidgets( + 'renders blurhash without image load for dead media thumbnail', + ( + tester, + ) async { + const blurhash = 'LEHV6nWB2yk8pyo0adR*.7kCMdnj'; + final videoWithDeadThumbnail = createTestVideoEvent( + id: 'test-dead-thumbnail', + thumbnailUrl: 'https://stream.divine.video/fa4a90a3-6a30-4dc6-9b9d-3f78551c9053/thumbnail.jpg', + blurhash: blurhash, + ); - await tester.pumpWidget( - MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - home: Scaffold( - body: VideoThumbnailWidget( - video: videoWithDeadThumbnail, - width: 200, - height: 200, + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: VideoThumbnailWidget( + video: videoWithDeadThumbnail, + width: 200, + height: 200, + ), ), ), - ), - ); + ); - expect(find.byType(PassiveAuthThumbnailImage), findsNothing); - expect(find.byType(VineCachedImage), findsNothing); - expect( - tester.widget(find.byType(BlurhashDisplay)).blurhash, - blurhash, - ); - }); + expect(find.byType(PassiveAuthThumbnailImage), findsNothing); + expect(find.byType(VineCachedImage), findsNothing); + expect( + tester.widget(find.byType(BlurhashDisplay)).blurhash, + blurhash, + ); + }, + ); testWidgets( 'updates missing metadata aspect ratio from the displayed cached image', @@ -321,8 +323,7 @@ void main() { (tester) async { final divineHostedVideo = createTestVideoEvent( id: 'test-divine', - thumbnailUrl: - 'https://media.divine.video/72d7eda61074b17e077fb9f4a8b48166cdeb65cb07e053aafa6e69d5fa165995.jpg', + thumbnailUrl: 'https://media.divine.video/72d7eda61074b17e077fb9f4a8b48166cdeb65cb07e053aafa6e69d5fa165995.jpg', ); await tester.pumpWidget( @@ -349,8 +350,7 @@ void main() { (tester) async { final divineHostedVideo = createTestVideoEvent( id: 'test-divine-dims', - thumbnailUrl: - 'https://media.divine.video/72d7eda61074b17e077fb9f4a8b48166cdeb65cb07e053aafa6e69d5fa165995.jpg', + thumbnailUrl: 'https://media.divine.video/72d7eda61074b17e077fb9f4a8b48166cdeb65cb07e053aafa6e69d5fa165995.jpg', ); await tester.pumpWidget( diff --git a/mobile/tools/generate_tests.dart b/mobile/tools/generate_tests.dart index fa8e898e2d..0d005c7712 100644 --- a/mobile/tools/generate_tests.dart +++ b/mobile/tools/generate_tests.dart @@ -2,6 +2,7 @@ // ABOUTME: Supports service, widget, integration, and benchmark test generation import 'dart:io'; + import 'package:args/args.dart'; import 'package:unified_logger/unified_logger.dart'; diff --git a/mobile/tools/test_upload.dart b/mobile/tools/test_upload.dart index e83711d935..6953662c3e 100644 --- a/mobile/tools/test_upload.dart +++ b/mobile/tools/test_upload.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; + import 'package:crypto/crypto.dart'; import 'package:dio/dio.dart'; import 'package:nostr_key_manager/nostr_key_manager.dart'; diff --git a/mobile/watch_codegen.sh b/mobile/watch_codegen.sh index 2c1cf8a4c9..276f14e02f 100755 --- a/mobile/watch_codegen.sh +++ b/mobile/watch_codegen.sh @@ -2,7 +2,7 @@ set -euo pipefail # Long-lived build_runner daemon. Run once in a separate terminal and leave -# it running. Subsequent edits to riverpod / freezed / json / drift inputs +# it running. Subsequent edits to Riverpod / JSON / Drift inputs # regenerate in seconds because the analyzer stays warm. Build scripts # detect that codegen is fresh and skip the full re-run. diff --git a/scripts/install-hooks.sh b/scripts/install-hooks.sh index 22de36b0f4..adcbd9414b 100755 --- a/scripts/install-hooks.sh +++ b/scripts/install-hooks.sh @@ -45,10 +45,9 @@ list_codegen_inputs() { [ -f "$abs_path" ] || continue local base_path="${abs_path%.dart}" - if grep -Eq '@Riverpod|@riverpod|@freezed|@Freezed|@JsonSerializable|@GenerateMocks|@DriftDatabase|@UseRowClass|@DataClassName|@UseMoor|@HiveType' "$abs_path" \ - || grep -Eq "part '.*\\.(g|freezed)\\.dart';" "$abs_path" \ + if grep -Eq '@Riverpod|@riverpod|@JsonSerializable|@GenerateMocks|@DriftDatabase|@UseRowClass|@DataClassName|@UseMoor|@HiveType' "$abs_path" \ + || grep -Eq "part '.*\\.g\\.dart';" "$abs_path" \ || [ -f "${base_path}.g.dart" ] \ - || [ -f "${base_path}.freezed.dart" ] \ || [ -f "${base_path}.mocks.dart" ]; then echo "$file" fi @@ -58,7 +57,7 @@ list_codegen_inputs() { capture_generated_status() { git status --porcelain -- "$REPO_ROOT/mobile" \ | awk '{print $2}' \ - | grep -E '^mobile/.*(\.g\.dart|\.freezed\.dart|\.mocks\.dart|\.types\.temp\.dart)$' \ + | grep -E '^mobile/.*(\.g\.dart|\.mocks\.dart|\.types\.temp\.dart)$' \ | sort -u || true } @@ -66,7 +65,7 @@ capture_generated_status() { STAGED_DART_FILES=$(git diff --cached --name-only --diff-filter=ACM \ | grep '^mobile/.*\.dart$' \ | grep -v '\.g\.dart$' \ - | grep -v '\.freezed\.dart$' || true) + || true) if [ -z "$STAGED_DART_FILES" ]; then exit 0 @@ -137,10 +136,9 @@ list_codegen_inputs() { [ -f "$abs_path" ] || continue local base_path="${abs_path%.dart}" - if grep -Eq '@Riverpod|@riverpod|@freezed|@Freezed|@JsonSerializable|@GenerateMocks|@DriftDatabase|@UseRowClass|@DataClassName|@UseMoor|@HiveType' "$abs_path" \ - || grep -Eq "part '.*\\.(g|freezed)\\.dart';" "$abs_path" \ + if grep -Eq '@Riverpod|@riverpod|@JsonSerializable|@GenerateMocks|@DriftDatabase|@UseRowClass|@DataClassName|@UseMoor|@HiveType' "$abs_path" \ + || grep -Eq "part '.*\\.g\\.dart';" "$abs_path" \ || [ -f "${base_path}.g.dart" ] \ - || [ -f "${base_path}.freezed.dart" ] \ || [ -f "${base_path}.mocks.dart" ]; then echo "$file" fi @@ -150,7 +148,7 @@ list_codegen_inputs() { capture_generated_status() { git -C "$REPO_ROOT" status --porcelain -- mobile \ | awk '{print $2}' \ - | grep -E '^mobile/.*(\.g\.dart|\.freezed\.dart|\.mocks\.dart|\.types\.temp\.dart)$' \ + | grep -E '^mobile/.*(\.g\.dart|\.mocks\.dart|\.types\.temp\.dart)$' \ | sort -u || true } @@ -232,7 +230,7 @@ fi # trigger stays conditional so unrelated pushes are not slowed. CHANGED_SERVICE_FILES=$(git -C "$REPO_ROOT" diff --name-only --diff-filter=ADR "$BASE_BRANCH"...HEAD 2>/dev/null \ | grep -E '^mobile/lib/services/.*\.dart$' \ - | grep -vE '\.(g|freezed)\.dart$' || true) + | grep -vE '\.(g|mocks)\.dart$' || true) CHANGED_TEST_FILES=$(git -C "$REPO_ROOT" diff --name-only --diff-filter=DR "$BASE_BRANCH"...HEAD 2>/dev/null \ | grep -E '^mobile/test/.*_test\.dart$' || true) if [ -n "$CHANGED_SERVICE_FILES" ] || [ -n "$CHANGED_TEST_FILES" ]; then @@ -364,8 +362,7 @@ fi # Get list of changed Dart files (excluding generated files) CHANGED_FILES=$(git -C "$REPO_ROOT" diff --name-only "$BASE_BRANCH"...HEAD 2>/dev/null \ | grep '^mobile/.*\.dart$' \ - | grep -v '\.g\.dart$' \ - | grep -v '\.freezed\.dart$' || true) + | grep -vE '\.(g|mocks)\.dart$' || true) if [ -z "$CHANGED_FILES" ]; then echo "No Dart files changed, skipping checks"