diff --git a/.ci.yaml b/.ci.yaml
index e44c1479c26b..275a8f1a179e 100644
--- a/.ci.yaml
+++ b/.ci.yaml
@@ -12,6 +12,27 @@ platform_properties:
linux:
properties:
os: Linux
+ linux_desktop:
+ properties:
+ os: Ubuntu
+ cores: "8"
+ device_type: none
+ dependencies: >-
+ [
+ {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"},
+ {"dependency": "cmake", "version": "build_id:8787856497187628321"},
+ {"dependency": "ninja", "version": "version:1.9.0"},
+ {"dependency": "curl", "version": "version:7.64.0"}
+ ]
+ linux_web:
+ properties:
+ os: Ubuntu
+ cores: "8"
+ device_type: none
+ dependencies: >-
+ [
+ {"dependency": "chrome_and_driver", "version": "version:114.0"}
+ ]
windows:
properties:
dependencies: >
@@ -48,7 +69,7 @@ platform_properties:
}
targets:
- ### Linux tasks ###
+ ### Linux-host general tasks ###
- name: Linux repo_tools_tests
recipe: packages/packages
timeout: 30
@@ -58,6 +79,78 @@ targets:
channel: master
version_file: flutter_master.version
+ - name: Linux analyze master
+ bringup: true # New target
+ recipe: packages/packages
+ timeout: 30
+ properties:
+ target_file: analyze.yaml
+ channel: master
+ version_file: flutter_master.version
+
+ - name: Linux analyze stable
+ bringup: true # New target
+ recipe: packages/packages
+ timeout: 30
+ properties:
+ target_file: analyze.yaml
+ channel: stable
+ version_file: flutter_stable.version
+
+ - name: Linux analyze_downgraded master
+ bringup: true # New target
+ recipe: packages/packages
+ timeout: 30
+ properties:
+ target_file: analyze_downgraded.yaml
+ channel: master
+ version_file: flutter_master.version
+
+ - name: Linux analyze_downgraded stable
+ bringup: true # New target
+ recipe: packages/packages
+ timeout: 30
+ properties:
+ target_file: analyze_downgraded.yaml
+ channel: stable
+ version_file: flutter_stable.version
+
+ ### Web tasks ###
+ - name: Linux_web web_build_all_packages master
+ recipe: packages/packages
+ timeout: 30
+ properties:
+ add_recipes_cq: "true"
+ version_file: flutter_master.version
+ target_file: web_build_all_packages.yaml
+ channel: master
+
+ - name: Linux_web web_build_all_packages stable
+ recipe: packages/packages
+ timeout: 30
+ properties:
+ version_file: flutter_stable.version
+ target_file: web_build_all_packages.yaml
+ channel: stable
+
+ ### Linux desktop tasks
+ - name: Linux_desktop build_all_packages master
+ recipe: packages/packages
+ timeout: 30
+ properties:
+ add_recipes_cq: "true"
+ version_file: flutter_master.version
+ target_file: linux_build_all_packages.yaml
+ channel: master
+
+ - name: Linux_desktop build_all_packages stable
+ recipe: packages/packages
+ timeout: 30
+ properties:
+ version_file: flutter_stable.version
+ target_file: linux_build_all_packages.yaml
+ channel: stable
+
### iOS+macOS tasks ###
# TODO(stuartmorgan): Move this to ARM once google_maps_flutter has ARM
# support. `pod lint` makes a synthetic target that doesn't respect the
@@ -144,7 +237,6 @@ targets:
target_file: ios_build_all_packages.yaml
- name: Mac_x64 ios_build_all_packages stable
- bringup: true # New target
recipe: packages/packages
timeout: 30
properties:
diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version
index 9ddb35ef55a1..2c19000f05f9 100644
--- a/.ci/flutter_master.version
+++ b/.ci/flutter_master.version
@@ -1 +1 @@
-8a5c22e282db5f45ffdef24752520894f18227b9
+fc8856eb80d306ac40563582a1212a07141d9001
diff --git a/.ci/flutter_stable.version b/.ci/flutter_stable.version
index ec36783b4ac1..f99b2a7b9d5e 100644
--- a/.ci/flutter_stable.version
+++ b/.ci/flutter_stable.version
@@ -1 +1 @@
-682aa387cfe4fbd71ccd5418b2c2a075729a1c66
+796c8ef79279f9c774545b3771238c3098dbefab
diff --git a/.ci/scripts/analyze_repo_tools.sh b/.ci/scripts/analyze_repo_tools.sh
new file mode 100755
index 000000000000..df2a87c04a98
--- /dev/null
+++ b/.ci/scripts/analyze_repo_tools.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+# Copyright 2013 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+set -e
+
+cd script/tool
+dart analyze --fatal-infos
diff --git a/.ci/scripts/dart_unit_tests_win32.sh b/.ci/scripts/dart_unit_tests_win32.sh
index 5fbe4764f6b3..2cd451c45caa 100755
--- a/.ci/scripts/dart_unit_tests_win32.sh
+++ b/.ci/scripts/dart_unit_tests_win32.sh
@@ -4,6 +4,6 @@
# found in the LICENSE file.
set -e
-dart ./script/tool/bin/flutter_plugin_tools.dart test \
+dart ./script/tool/bin/flutter_plugin_tools.dart dart-test \
--exclude=script/configs/windows_unit_tests_exceptions.yaml \
--packages-for-branch --log-timing
diff --git a/.ci/scripts/pathified_analyze.sh b/.ci/scripts/pathified_analyze.sh
new file mode 100755
index 000000000000..2942cf7d57b8
--- /dev/null
+++ b/.ci/scripts/pathified_analyze.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+# Copyright 2013 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+set -e
+
+# Pathify the dependencies on changed packages (excluding major version
+# changes, which won't affect clients).
+./script/tool_runner.sh make-deps-path-based --target-dependencies-with-non-breaking-updates
+# This uses --run-on-dirty-packages rather than --packages-for-branch
+# since only the packages changed by 'make-deps-path-based' need to be
+# re-checked.
+dart ./script/tool/bin/flutter_plugin_tools.dart analyze --run-on-dirty-packages \
+ --log-timing --custom-analysis=script/configs/custom_analysis.yaml
+# Restore the tree to a clean state, to avoid accidental issues if
+# other script steps are added to the enclosing task.
+git checkout .
diff --git a/.ci/targets/analyze.yaml b/.ci/targets/analyze.yaml
new file mode 100644
index 000000000000..25791d3c898c
--- /dev/null
+++ b/.ci/targets/analyze.yaml
@@ -0,0 +1,15 @@
+tasks:
+ - name: prepare tool
+ script: .ci/scripts/prepare_tool.sh
+ - name: analyze repo tools
+ script: .ci/scripts/analyze_repo_tools.sh
+ - name: analyze
+ script: script/tool_runner.sh
+ # DO NOT change the custom-analysis argument here without changing the Dart repo.
+ # See the comment in script/configs/custom_analysis.yaml for details.
+ args: ["analyze", "--custom-analysis=script/configs/custom_analysis.yaml"]
+ # Re-run analysis with path-based dependencies to ensure that publishing
+ # the changes won't break analysis of other packages in the respository
+ # that depend on it.
+ - name: analyze - pathified
+ script: .ci/scripts/pathified_analyze.sh
diff --git a/.ci/targets/analyze_downgraded.yaml b/.ci/targets/analyze_downgraded.yaml
new file mode 100644
index 000000000000..46bc111b30c5
--- /dev/null
+++ b/.ci/targets/analyze_downgraded.yaml
@@ -0,0 +1,10 @@
+tasks:
+ - name: prepare tool
+ script: .ci/scripts/prepare_tool.sh
+ # Does a sanity check that packages pass analysis with the lowest possible
+ # versions of all dependencies. This is to catch cases where we add use of
+ # new APIs but forget to update minimum versions of dependencies to where
+ # those APIs are introduced.
+ - name: analyze - downgraded
+ script: script/tool_runner.sh
+ args: ["analyze", "--downgrade", "--custom-analysis=script/configs/custom_analysis.yaml"]
diff --git a/.ci/targets/linux_build_all_packages.yaml b/.ci/targets/linux_build_all_packages.yaml
new file mode 100644
index 000000000000..b54f7b1e56c2
--- /dev/null
+++ b/.ci/targets/linux_build_all_packages.yaml
@@ -0,0 +1,11 @@
+tasks:
+ - name: prepare tool
+ script: .ci/scripts/prepare_tool.sh
+ - name: create all_packages app
+ script: .ci/scripts/create_all_packages_app.sh
+ - name: build all_packages for Linux debug
+ script: .ci/scripts/build_all_packages_app.sh
+ args: ["linux", "debug"]
+ - name: build all_packages for Linux release
+ script: .ci/scripts/build_all_packages_app.sh
+ args: ["linux", "release"]
diff --git a/.ci/targets/web_build_all_packages.yaml b/.ci/targets/web_build_all_packages.yaml
new file mode 100644
index 000000000000..d3b7ae010725
--- /dev/null
+++ b/.ci/targets/web_build_all_packages.yaml
@@ -0,0 +1,10 @@
+tasks:
+ - name: prepare tool
+ script: .ci/scripts/prepare_tool.sh
+ - name: create all_packages app
+ script: .ci/scripts/create_all_packages_app.sh
+ # No debug version, unlike the other platforms, since web does not support
+ # debug builds.
+ - name: build all_packages app for Web release
+ script: .ci/scripts/build_all_packages_app.sh
+ args: ["web", "release"]
diff --git a/.cirrus.yml b/.cirrus.yml
index c8a7af48cce9..0808a1cabb52 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -22,13 +22,6 @@ tool_setup_template: &TOOL_SETUP_TEMPLATE
tool_setup_script:
- .ci/scripts/prepare_tool.sh
-macos_template: &MACOS_TEMPLATE
- # Only one macOS task can run in parallel without credits, so use them for
- # PRs on macOS.
- use_compute_credits: $CIRRUS_USER_COLLABORATOR == 'true'
- macos_instance:
- image: ghcr.io/cirruslabs/macos-ventura-xcode:14.3
-
flutter_upgrade_template: &FLUTTER_UPGRADE_TEMPLATE
upgrade_flutter_script:
# Channels that are part of our normal test matrix use a pinned,
@@ -183,22 +176,7 @@ task:
env:
CIRRUS_CLONE_SUBMODULES: true
script: ./script/tool_runner.sh update-excerpts --fail-on-change
- ### Web tasks ###
- - name: web-build_all_packages
- env:
- BUILD_ALL_ARGS: "web"
- matrix:
- CHANNEL: "master"
- CHANNEL: "stable"
- << : *BUILD_ALL_PACKAGES_APP_TEMPLATE
### Linux desktop tasks ###
- - name: linux-build_all_packages
- env:
- BUILD_ALL_ARGS: "linux"
- matrix:
- CHANNEL: "master"
- CHANNEL: "stable"
- << : *BUILD_ALL_PACKAGES_APP_TEMPLATE
- name: linux-platform_tests
# Don't run full platform tests on both channels in pre-submit.
skip: $CIRRUS_PR != '' && $CHANNEL == 'stable'
@@ -238,13 +216,13 @@ task:
CHANNEL: "master"
CHANNEL: "stable"
unit_test_script:
- - ./script/tool_runner.sh test --exclude=script/configs/dart_unit_tests_exceptions.yaml
+ - ./script/tool_runner.sh dart-test --exclude=script/configs/dart_unit_tests_exceptions.yaml
pathified_unit_test_script:
# Run tests with path-based dependencies to ensure that publishing
# the changes won't break tests of other packages in the repository
# that depend on it.
- ./script/tool_runner.sh make-deps-path-based --target-dependencies-with-non-breaking-updates
- - $PLUGIN_TOOL_COMMAND test --run-on-dirty-packages --exclude=script/configs/dart_unit_tests_exceptions.yaml
+ - $PLUGIN_TOOL_COMMAND dart-test --run-on-dirty-packages --exclude=script/configs/dart_unit_tests_exceptions.yaml
- name: linux-custom_package_tests
env:
PATH: $PATH:/usr/local/bin
@@ -335,24 +313,3 @@ task:
- cd ../..
- flutter packages get
- dart testing/web_benchmarks_test.dart
-
-# macOS tasks.
-task:
- << : *FLUTTER_UPGRADE_TEMPLATE
- << : *MACOS_TEMPLATE
- matrix:
- - name: macos-custom_package_tests
- env:
- PATH: $PATH:/usr/local/bin
- matrix:
- CHANNEL: "master"
- CHANNEL: "stable"
- # Create an iPhone 13, to match what is available on LUCI, since Pigeon tests
- # currently have a hard-coded device.
- create_simulator_script:
- - xcrun simctl list
- - xcrun simctl create "iPhone 13" com.apple.CoreSimulator.SimDeviceType.iPhone-13 com.apple.CoreSimulator.SimRuntime.iOS-16-4
- local_tests_script:
- # script/configs/linux_only_custom_test.yaml
- # Custom tests need Chrome for these packages. (They run in linux-custom_package_tests)
- - ./script/tool_runner.sh custom-test --exclude=script/configs/linux_only_custom_test.yaml
diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml
index e48c050fb88d..a474d2c4b5e4 100644
--- a/.github/workflows/pull_request_label.yml
+++ b/.github/workflows/pull_request_label.yml
@@ -21,7 +21,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- - uses: actions/labeler@ba790c862c380240c6d5e7427be5ace9a05c754b
+ - uses: actions/labeler@9fcb2c2f5584144ca754f8bfe8c6f81e77753375
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
diff --git a/CODEOWNERS b/CODEOWNERS
index e81c2fb6ae84..dfc8a29c749d 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -83,12 +83,14 @@ packages/webview_flutter/webview_flutter_wkwebview/** @cyanglaz
# - Linux
packages/file_selector/file_selector_linux/** @cbracken
+packages/image_picker/image_picker_linux/** @cbracken
packages/path_provider/path_provider_linux/** @cbracken
packages/shared_preferences/shared_preferences_linux/** @cbracken
packages/url_launcher/url_launcher_linux/** @cbracken
# - macOS
packages/file_selector/file_selector_macos/** @cbracken
+packages/image_picker/image_picker_macos/** @cbracken
packages/url_launcher/url_launcher_macos/** @cbracken
# - Windows
diff --git a/packages/animations/example/ios/Runner/Info.plist b/packages/animations/example/ios/Runner/Info.plist
index a060db61e461..1251b459385b 100644
--- a/packages/animations/example/ios/Runner/Info.plist
+++ b/packages/animations/example/ios/Runner/Info.plist
@@ -39,7 +39,5 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
diff --git a/packages/camera/camera/example/ios/Runner/Info.plist b/packages/camera/camera/example/ios/Runner/Info.plist
index bacd9e54f1ea..13e41200aca1 100644
--- a/packages/camera/camera/example/ios/Runner/Info.plist
+++ b/packages/camera/camera/example/ios/Runner/Info.plist
@@ -50,8 +50,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md
index 8bfdd7f0b1fe..c853b8a4b1be 100644
--- a/packages/camera/camera_android/CHANGELOG.md
+++ b/packages/camera/camera_android/CHANGELOG.md
@@ -1,6 +1,7 @@
## NEXT
* Fixes unawaited_futures violations.
+* Removes duplicate line in `MediaRecorderBuilder.java`.
## 0.10.8+2
diff --git a/packages/camera/camera_android/android/build.gradle b/packages/camera/camera_android/android/build.gradle
index ef8d9e297674..24a736e0e908 100644
--- a/packages/camera/camera_android/android/build.gradle
+++ b/packages/camera/camera_android/android/build.gradle
@@ -51,6 +51,7 @@ android {
unitTests.includeAndroidResources = true
unitTests.returnDefaultValues = true
unitTests.all {
+ jvmArgs "-Xmx1g"
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
@@ -65,5 +66,5 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:5.0.0'
testImplementation 'androidx.test:core:1.4.0'
- testImplementation 'org.robolectric:robolectric:4.5'
+ testImplementation 'org.robolectric:robolectric:4.10.3'
}
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
index f55552edf898..966019bb1431 100644
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
+++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
@@ -92,7 +92,6 @@ public MediaRecorder build() throws IOException, NullPointerException, IndexOutO
mediaRecorder.setVideoEncodingBitRate(videoProfile.getBitrate());
mediaRecorder.setVideoFrameRate(videoProfile.getFrameRate());
mediaRecorder.setVideoSize(videoProfile.getWidth(), videoProfile.getHeight());
- mediaRecorder.setVideoSize(videoProfile.getWidth(), videoProfile.getHeight());
} else if (camcorderProfile != null) {
mediaRecorder.setOutputFormat(camcorderProfile.fileFormat);
if (enableAudio) {
diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
index dbc352d697a4..fe4dcd795fed 100644
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
+++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
@@ -91,8 +91,9 @@ public void beforeLegacy() {
public void before() {
mockProfileLow = mock(EncoderProfiles.class);
EncoderProfiles mockProfile = mock(EncoderProfiles.class);
- EncoderProfiles.VideoProfile mockVideoProfile = mock(EncoderProfiles.VideoProfile.class);
- List mockVideoProfilesList = List.of(mockVideoProfile);
+ List mockVideoProfilesList =
+ new ArrayList();
+ mockVideoProfilesList.add(null);
mockedStaticProfile
.when(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_HIGH))
@@ -117,8 +118,6 @@ public void before() {
.thenReturn(mockProfileLow);
when(mockProfile.getVideoProfiles()).thenReturn(mockVideoProfilesList);
- when(mockVideoProfile.getHeight()).thenReturn(100);
- when(mockVideoProfile.getWidth()).thenReturn(100);
}
@After
@@ -386,7 +385,7 @@ public void computeBestPreviewSize_shouldUseLegacyBehaviorWhenEncoderProfilesNul
@Config(minSdk = 31)
@Test
public void resolutionFeatureShouldUseLegacyBehaviorWhenEncoderProfilesNull() {
- beforeLegacy();
+ before();
try (MockedStatic mockedResolutionFeature =
mockStatic(ResolutionFeature.class)) {
mockedResolutionFeature
diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
index 6cc58ee823d9..f37de01f5e7c 100644
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
+++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
@@ -78,9 +78,9 @@ public void build_shouldSetValuesInCorrectOrderWhenAudioIsDisabledLegacy() throw
public void build_shouldSetValuesInCorrectOrderWhenAudioIsDisabled() throws IOException {
EncoderProfiles recorderProfile = mock(EncoderProfiles.class);
List mockVideoProfiles =
- List.of(mock(EncoderProfiles.VideoProfile.class));
+ List.of(getEmptyEncoderProfilesVideoProfile());
List mockAudioProfiles =
- List.of(mock(EncoderProfiles.AudioProfile.class));
+ List.of(getEmptyEncoderProfilesAudioProfile());
MediaRecorderBuilder.MediaRecorderFactory mockFactory =
mock(MediaRecorderBuilder.MediaRecorderFactory.class);
MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
@@ -172,9 +172,9 @@ public void build_shouldSetValuesInCorrectOrderWhenAudioIsEnabledLegacy() throws
public void build_shouldSetValuesInCorrectOrderWhenAudioIsEnabled() throws IOException {
EncoderProfiles recorderProfile = mock(EncoderProfiles.class);
List mockVideoProfiles =
- List.of(mock(EncoderProfiles.VideoProfile.class));
+ List.of(getEmptyEncoderProfilesVideoProfile());
List mockAudioProfiles =
- List.of(mock(EncoderProfiles.AudioProfile.class));
+ List.of(getEmptyEncoderProfilesAudioProfile());
MediaRecorderBuilder.MediaRecorderFactory mockFactory =
mock(MediaRecorderBuilder.MediaRecorderFactory.class);
MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
@@ -224,4 +224,32 @@ private CamcorderProfile getEmptyCamcorderProfile() {
return null;
}
+
+ private EncoderProfiles.VideoProfile getEmptyEncoderProfilesVideoProfile() {
+ try {
+ Constructor constructor =
+ EncoderProfiles.VideoProfile.class.getDeclaredConstructor(
+ int.class, int.class, int.class, int.class, int.class, int.class);
+
+ constructor.setAccessible(true);
+ return constructor.newInstance(0, 0, 0, 0, 0, 0);
+ } catch (Exception ignored) {
+ }
+
+ return null;
+ }
+
+ private EncoderProfiles.AudioProfile getEmptyEncoderProfilesAudioProfile() {
+ try {
+ Constructor constructor =
+ EncoderProfiles.AudioProfile.class.getDeclaredConstructor(
+ int.class, int.class, int.class, int.class, int.class);
+
+ constructor.setAccessible(true);
+ return constructor.newInstance(0, 0, 0, 0, 0);
+ } catch (Exception ignored) {
+ }
+
+ return null;
+ }
}
diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index 5912f9834548..2980f18c19bf 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.0+7
+
+* Updates Guava version to 32.0.1.
+
## 0.5.0+6
* Updates Guava version to 32.0.0.
diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle
index 83871babe062..f10f13336081 100644
--- a/packages/camera/camera_android_camerax/android/build.gradle
+++ b/packages/camera/camera_android_camerax/android/build.gradle
@@ -66,7 +66,7 @@ dependencies {
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-video:${camerax_version}"
- implementation 'com.google.guava:guava:32.0.0-android'
+ implementation 'com.google.guava:guava:32.0.1-android'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:5.0.0'
testImplementation 'androidx.test:core:1.4.0'
diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml
index 5e4ea92ba6c5..d0364186b281 100644
--- a/packages/camera/camera_android_camerax/pubspec.yaml
+++ b/packages/camera/camera_android_camerax/pubspec.yaml
@@ -2,7 +2,7 @@ name: camera_android_camerax
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.5.0+6
+version: 0.5.0+7
environment:
sdk: ">=2.19.0 <4.0.0"
diff --git a/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist b/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist
index ca93baac7012..adb62fb7803d 100644
--- a/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist
+++ b/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist
@@ -52,8 +52,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/dynamic_layouts/example/ios/Runner/Info.plist b/packages/dynamic_layouts/example/ios/Runner/Info.plist
index 7f553465b77e..5458fc4188bf 100644
--- a/packages/dynamic_layouts/example/ios/Runner/Info.plist
+++ b/packages/dynamic_layouts/example/ios/Runner/Info.plist
@@ -41,8 +41,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Info.plist b/packages/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Info.plist
index aa6d84f63af1..28ab78e39981 100644
--- a/packages/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Info.plist
+++ b/packages/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Info.plist
@@ -39,8 +39,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/file_selector/file_selector/example/ios/Runner/Info.plist b/packages/file_selector/file_selector/example/ios/Runner/Info.plist
index 7f553465b77e..5458fc4188bf 100644
--- a/packages/file_selector/file_selector/example/ios/Runner/Info.plist
+++ b/packages/file_selector/file_selector/example/ios/Runner/Info.plist
@@ -41,8 +41,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/file_selector/file_selector_ios/example/ios/Runner/Info.plist b/packages/file_selector/file_selector_ios/example/ios/Runner/Info.plist
index 2bf6e923d3b6..67d621a7fc80 100644
--- a/packages/file_selector/file_selector_ios/example/ios/Runner/Info.plist
+++ b/packages/file_selector/file_selector_ios/example/ios/Runner/Info.plist
@@ -41,8 +41,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md
index 6ff69f593d36..857f82fa02ae 100644
--- a/packages/flutter_adaptive_scaffold/CHANGELOG.md
+++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.1.5
+
+* Added support for Right-to-left (RTL) directionality.
+* Fixes stale ignore: prefer_const_constructors.
+* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
+
## 0.1.4
* Use Material 3 NavigationBar instead of BottomNavigationBar
diff --git a/packages/flutter_adaptive_scaffold/README.md b/packages/flutter_adaptive_scaffold/README.md
index 612e62c4bc92..e9324afbf1b1 100644
--- a/packages/flutter_adaptive_scaffold/README.md
+++ b/packages/flutter_adaptive_scaffold/README.md
@@ -137,110 +137,108 @@ displayed and the entrance animation and exit animation.
```dart
- // AdaptiveLayout has a number of slots that take SlotLayouts and these
- // SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs.
- return AdaptiveLayout(
- // Primary navigation config has nothing from 0 to 600 dp screen width,
- // then an unextended NavigationRail with no labels and just icons then an
- // extended NavigationRail with both icons and labels.
- primaryNavigation: SlotLayout(
- config: {
- Breakpoints.medium: SlotLayout.from(
- inAnimation: AdaptiveScaffold.leftOutIn,
- key: const Key('Primary Navigation Medium'),
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- selectedIndex: selectedNavigation,
- onDestinationSelected: (int newIndex) {
- setState(() {
- selectedNavigation = newIndex;
- });
- },
- leading: const Icon(Icons.menu),
- destinations: destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- backgroundColor: navRailTheme.backgroundColor,
- selectedIconTheme: navRailTheme.selectedIconTheme,
- unselectedIconTheme: navRailTheme.unselectedIconTheme,
- selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
- unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
- ),
- ),
- Breakpoints.large: SlotLayout.from(
- key: const Key('Primary Navigation Large'),
- inAnimation: AdaptiveScaffold.leftOutIn,
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- selectedIndex: selectedNavigation,
- onDestinationSelected: (int newIndex) {
- setState(() {
- selectedNavigation = newIndex;
- });
- },
- extended: true,
- leading: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: const [
- Text(
- 'REPLY',
- style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)),
- ),
- Icon(Icons.menu_open)
- ],
- ),
- destinations: destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- trailing: trailingNavRail,
- backgroundColor: navRailTheme.backgroundColor,
- selectedIconTheme: navRailTheme.selectedIconTheme,
- unselectedIconTheme: navRailTheme.unselectedIconTheme,
- selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
- unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
- ),
- ),
- },
+// AdaptiveLayout has a number of slots that take SlotLayouts and these
+// SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs.
+return AdaptiveLayout(
+ // Primary navigation config has nothing from 0 to 600 dp screen width,
+ // then an unextended NavigationRail with no labels and just icons then an
+ // extended NavigationRail with both icons and labels.
+ primaryNavigation: SlotLayout(
+ config: {
+ Breakpoints.medium: SlotLayout.from(
+ inAnimation: AdaptiveScaffold.leftOutIn,
+ key: const Key('Primary Navigation Medium'),
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ selectedIndex: selectedNavigation,
+ onDestinationSelected: (int newIndex) {
+ setState(() {
+ selectedNavigation = newIndex;
+ });
+ },
+ leading: const Icon(Icons.menu),
+ destinations: destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ backgroundColor: navRailTheme.backgroundColor,
+ selectedIconTheme: navRailTheme.selectedIconTheme,
+ unselectedIconTheme: navRailTheme.unselectedIconTheme,
+ selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
+ unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
+ ),
),
- // Body switches between a ListView and a GridView from small to medium
- // breakpoints and onwards.
- body: SlotLayout(
- config: {
- Breakpoints.small: SlotLayout.from(
- key: const Key('Body Small'),
- builder: (_) => ListView.builder(
- itemCount: children.length,
- itemBuilder: (BuildContext context, int index) => children[index],
- ),
+ Breakpoints.large: SlotLayout.from(
+ key: const Key('Primary Navigation Large'),
+ inAnimation: AdaptiveScaffold.leftOutIn,
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ selectedIndex: selectedNavigation,
+ onDestinationSelected: (int newIndex) {
+ setState(() {
+ selectedNavigation = newIndex;
+ });
+ },
+ extended: true,
+ leading: const Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: [
+ Text(
+ 'REPLY',
+ style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)),
+ ),
+ Icon(Icons.menu_open)
+ ],
),
- Breakpoints.mediumAndUp: SlotLayout.from(
- key: const Key('Body Medium'),
- builder: (_) =>
- GridView.count(crossAxisCount: 2, children: children),
- )
- },
+ destinations: destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ trailing: trailingNavRail,
+ backgroundColor: navRailTheme.backgroundColor,
+ selectedIconTheme: navRailTheme.selectedIconTheme,
+ unselectedIconTheme: navRailTheme.unselectedIconTheme,
+ selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
+ unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
+ ),
),
- // BottomNavigation is only active in small views defined as under 600 dp
- // width.
- bottomNavigation: SlotLayout(
- config: {
- Breakpoints.small: SlotLayout.from(
- key: const Key('Bottom Navigation Small'),
- inAnimation: AdaptiveScaffold.bottomToTop,
- outAnimation: AdaptiveScaffold.topToBottom,
- builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
- destinations: destinations,
- currentIndex: selectedNavigation,
- onDestinationSelected: (int newIndex) {
- setState(() {
- selectedNavigation = newIndex;
- });
- },
- ),
- )
- },
+ },
+ ),
+ // Body switches between a ListView and a GridView from small to medium
+ // breakpoints and onwards.
+ body: SlotLayout(
+ config: {
+ Breakpoints.small: SlotLayout.from(
+ key: const Key('Body Small'),
+ builder: (_) => ListView.builder(
+ itemCount: children.length,
+ itemBuilder: (BuildContext context, int index) => children[index],
+ ),
),
- );
- }
-}
+ Breakpoints.mediumAndUp: SlotLayout.from(
+ key: const Key('Body Medium'),
+ builder: (_) =>
+ GridView.count(crossAxisCount: 2, children: children),
+ )
+ },
+ ),
+ // BottomNavigation is only active in small views defined as under 600 dp
+ // width.
+ bottomNavigation: SlotLayout(
+ config: {
+ Breakpoints.small: SlotLayout.from(
+ key: const Key('Bottom Navigation Small'),
+ inAnimation: AdaptiveScaffold.bottomToTop,
+ outAnimation: AdaptiveScaffold.topToBottom,
+ builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
+ destinations: destinations,
+ currentIndex: selectedNavigation,
+ onDestinationSelected: (int newIndex) {
+ setState(() {
+ selectedNavigation = newIndex;
+ });
+ },
+ ),
+ )
+ },
+ ),
+);
```
Both of the examples shown here produce the same output:
diff --git a/packages/flutter_adaptive_scaffold/example/ios/Runner/Info.plist b/packages/flutter_adaptive_scaffold/example/ios/Runner/Info.plist
index 7f553465b77e..5458fc4188bf 100644
--- a/packages/flutter_adaptive_scaffold/example/ios/Runner/Info.plist
+++ b/packages/flutter_adaptive_scaffold/example/ios/Runner/Info.plist
@@ -41,8 +41,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart b/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart
index eb9522041fce..77b5cd78d9fe 100644
--- a/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart
+++ b/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart
@@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// TODO(goderbauer): Remove this ignore when this package requires Flutter 3.8 or later.
-// ignore_for_file: prefer_const_constructors
-
import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
@@ -58,8 +55,8 @@ class _MyHomePageState extends State {
children: [
const Divider(color: Colors.black),
const SizedBox(height: 10),
- Row(
- children: const [
+ const Row(
+ children: [
SizedBox(width: 27),
Text('Folders', style: TextStyle(fontSize: 16)),
],
@@ -74,8 +71,8 @@ class _MyHomePageState extends State {
iconSize: 21,
),
const SizedBox(width: 21),
- Flexible(
- child: const Text(
+ const Flexible(
+ child: Text(
'Freelance',
overflow: TextOverflow.ellipsis,
),
@@ -92,8 +89,8 @@ class _MyHomePageState extends State {
iconSize: 21,
),
const SizedBox(width: 21),
- Flexible(
- child: const Text(
+ const Flexible(
+ child: Text(
'Mortgage',
overflow: TextOverflow.ellipsis,
),
@@ -198,9 +195,9 @@ class _MyHomePageState extends State {
});
},
extended: true,
- leading: Row(
+ leading: const Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: const [
+ children: [
Text(
'REPLY',
style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)),
@@ -260,6 +257,6 @@ class _MyHomePageState extends State {
},
),
);
- // #enddocregion
+ // #enddocregion Example
}
}
diff --git a/packages/flutter_adaptive_scaffold/example/lib/main.dart b/packages/flutter_adaptive_scaffold/example/lib/main.dart
index 994b9c8b835c..4aac90d8bc93 100644
--- a/packages/flutter_adaptive_scaffold/example/lib/main.dart
+++ b/packages/flutter_adaptive_scaffold/example/lib/main.dart
@@ -52,6 +52,9 @@ class _MyHomePageState extends State
// the navigation elements.
ValueNotifier showGridView = ValueNotifier(false);
+ // Override the application's directionality.
+ TextDirection directionalityOverride = TextDirection.ltr;
+
// The index of the selected mail card.
int? selected;
@@ -118,70 +121,96 @@ class _MyHomePageState extends State
@override
Widget build(BuildContext context) {
- final Widget trailingNavRail = Column(
- children: [
- const Divider(color: Colors.white, thickness: 1.5),
- const SizedBox(height: 10),
- Row(children: [
- const SizedBox(width: 22),
- Text('Folders',
- style: TextStyle(fontSize: 13, color: Colors.grey[700]))
- ]),
- const SizedBox(height: 22),
- Row(
- children: [
- const SizedBox(width: 16),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.folder_copy_outlined),
- iconSize: 21,
- ),
- const SizedBox(width: 21),
- const Text('Freelance', overflow: TextOverflow.ellipsis),
- ],
- ),
- const SizedBox(height: 16),
- Row(
- children: [
- const SizedBox(width: 16),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.folder_copy_outlined),
- iconSize: 21,
- ),
- const SizedBox(width: 21),
- const Text('Mortgage', overflow: TextOverflow.ellipsis),
- ],
- ),
- const SizedBox(height: 16),
- Row(
- children: [
- const SizedBox(width: 16),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.folder_copy_outlined),
- iconSize: 21,
- ),
- const SizedBox(width: 21),
- const Flexible(
- child: Text('Taxes', overflow: TextOverflow.ellipsis))
- ],
- ),
- const SizedBox(height: 16),
- Row(
- children: [
- const SizedBox(width: 16),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.folder_copy_outlined),
- iconSize: 21,
+ final Widget trailingNavRail = Expanded(
+ child: Column(
+ children: [
+ const Divider(color: Colors.white, thickness: 1.5),
+ const SizedBox(height: 10),
+ Row(children: [
+ const SizedBox(width: 22),
+ Text('Folders',
+ style: TextStyle(fontSize: 13, color: Colors.grey[700]))
+ ]),
+ const SizedBox(height: 22),
+ Row(
+ children: [
+ const SizedBox(width: 16),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.folder_copy_outlined),
+ iconSize: 21,
+ ),
+ const SizedBox(width: 21),
+ const Text('Freelance', overflow: TextOverflow.ellipsis),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ const SizedBox(width: 16),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.folder_copy_outlined),
+ iconSize: 21,
+ ),
+ const SizedBox(width: 21),
+ const Text('Mortgage', overflow: TextOverflow.ellipsis),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ const SizedBox(width: 16),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.folder_copy_outlined),
+ iconSize: 21,
+ ),
+ const SizedBox(width: 21),
+ const Flexible(
+ child: Text('Taxes', overflow: TextOverflow.ellipsis))
+ ],
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ const SizedBox(width: 16),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.folder_copy_outlined),
+ iconSize: 21,
+ ),
+ const SizedBox(width: 21),
+ const Flexible(
+ child: Text('Receipts', overflow: TextOverflow.ellipsis))
+ ],
+ ),
+ Expanded(
+ child: Align(
+ alignment: Alignment.bottomCenter,
+ child: SwitchListTile.adaptive(
+ title: const Text(
+ 'Directionality',
+ style: TextStyle(fontSize: 12),
+ ),
+ subtitle: Text(
+ directionalityOverride == TextDirection.ltr ? 'LTR' : 'RTL',
+ ),
+ value: directionalityOverride == TextDirection.ltr,
+ onChanged: (bool value) {
+ setState(() {
+ if (value) {
+ directionalityOverride = TextDirection.ltr;
+ } else {
+ directionalityOverride = TextDirection.rtl;
+ }
+ });
+ },
+ ),
),
- const SizedBox(width: 21),
- const Flexible(
- child: Text('Receipts', overflow: TextOverflow.ellipsis))
- ],
- ),
- ],
+ ),
+ ],
+ ),
);
// These are the destinations used within the AdaptiveScaffold navigation
@@ -208,134 +237,137 @@ class _MyHomePageState extends State
// Updating the listener value.
showGridView.value = Breakpoints.mediumAndUp.isActive(context);
- return Scaffold(
- backgroundColor: const Color.fromARGB(255, 234, 227, 241),
- // Usage of AdaptiveLayout suite begins here. AdaptiveLayout takes
- // LayoutSlots for its variety of screen slots.
- body: AdaptiveLayout(
- // Each SlotLayout has a config which maps Breakpoints to
- // SlotLayoutConfigs.
- primaryNavigation: SlotLayout(
- config: {
- // The breakpoint used here is from the Breakpoints class but custom
- // Breakpoints can be defined by extending the Breakpoint class
- Breakpoints.medium: SlotLayout.from(
- // Every SlotLayoutConfig takes a key and a builder. The builder
- // is to save memory that would be spent on initialization.
- key: const Key('primaryNavigation'),
- builder: (_) {
- return AdaptiveScaffold.standardNavigationRail(
- // Usually it would be easier to use a builder from
- // AdaptiveScaffold for these types of navigation but this
- // navigation has custom staggered item animations.
+ return Directionality(
+ textDirection: directionalityOverride,
+ child: Scaffold(
+ backgroundColor: const Color.fromARGB(255, 234, 227, 241),
+ // Usage of AdaptiveLayout suite begins here. AdaptiveLayout takes
+ // LayoutSlots for its variety of screen slots.
+ body: AdaptiveLayout(
+ // Each SlotLayout has a config which maps Breakpoints to
+ // SlotLayoutConfigs.
+ primaryNavigation: SlotLayout(
+ config: {
+ // The breakpoint used here is from the Breakpoints class but custom
+ // Breakpoints can be defined by extending the Breakpoint class
+ Breakpoints.medium: SlotLayout.from(
+ // Every SlotLayoutConfig takes a key and a builder. The builder
+ // is to save memory that would be spent on initialization.
+ key: const Key('primaryNavigation'),
+ builder: (_) {
+ return AdaptiveScaffold.standardNavigationRail(
+ // Usually it would be easier to use a builder from
+ // AdaptiveScaffold for these types of navigation but this
+ // navigation has custom staggered item animations.
+ onDestinationSelected: (int index) {
+ setState(() {
+ _navigationIndex = index;
+ });
+ },
+ selectedIndex: _navigationIndex,
+ leading: ScaleTransition(
+ scale: _articleIconSlideController,
+ child: const _MediumComposeIcon(),
+ ),
+ backgroundColor: const Color.fromARGB(0, 255, 255, 255),
+ destinations: [
+ slideInNavigationItem(
+ begin: -1,
+ controller: _inboxIconSlideController,
+ icon: Icons.inbox,
+ label: 'Inbox',
+ ),
+ slideInNavigationItem(
+ begin: -2,
+ controller: _articleIconSlideController,
+ icon: Icons.article_outlined,
+ label: 'Articles',
+ ),
+ slideInNavigationItem(
+ begin: -3,
+ controller: _chatIconSlideController,
+ icon: Icons.chat_bubble_outline,
+ label: 'Chat',
+ ),
+ slideInNavigationItem(
+ begin: -4,
+ controller: _videoIconSlideController,
+ icon: Icons.video_call_outlined,
+ label: 'Video',
+ )
+ ],
+ );
+ },
+ ),
+ Breakpoints.large: SlotLayout.from(
+ key: const Key('Large primaryNavigation'),
+ // The AdaptiveScaffold builder here greatly simplifies
+ // navigational elements.
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ leading: const _LargeComposeIcon(),
onDestinationSelected: (int index) {
setState(() {
_navigationIndex = index;
});
},
selectedIndex: _navigationIndex,
- leading: ScaleTransition(
- scale: _articleIconSlideController,
- child: const _MediumComposeIcon(),
- ),
- backgroundColor: const Color.fromARGB(0, 255, 255, 255),
- destinations: [
- slideInNavigationItem(
- begin: -1,
- controller: _inboxIconSlideController,
- icon: Icons.inbox,
- label: 'Inbox',
- ),
- slideInNavigationItem(
- begin: -2,
- controller: _articleIconSlideController,
- icon: Icons.article_outlined,
- label: 'Articles',
- ),
- slideInNavigationItem(
- begin: -3,
- controller: _chatIconSlideController,
- icon: Icons.chat_bubble_outline,
- label: 'Chat',
- ),
- slideInNavigationItem(
- begin: -4,
- controller: _videoIconSlideController,
- icon: Icons.video_call_outlined,
- label: 'Video',
- )
- ],
- );
- },
- ),
- Breakpoints.large: SlotLayout.from(
- key: const Key('Large primaryNavigation'),
- // The AdaptiveScaffold builder here greatly simplifies
- // navigational elements.
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- leading: const _LargeComposeIcon(),
- onDestinationSelected: (int index) {
- setState(() {
- _navigationIndex = index;
- });
- },
- selectedIndex: _navigationIndex,
- trailing: trailingNavRail,
- extended: true,
- destinations: destinations.map((_) {
- return AdaptiveScaffold.toRailDestination(_);
- }).toList(),
+ trailing: trailingNavRail,
+ extended: true,
+ destinations: destinations.map((_) {
+ return AdaptiveScaffold.toRailDestination(_);
+ }).toList(),
+ ),
),
- ),
- },
- ),
- body: SlotLayout(
- config: {
- Breakpoints.standard: SlotLayout.from(
- key: const Key('body'),
- // The conditional here is for navigation screens. The first
- // screen shows the main screen and every other screen shows
- // ExamplePage.
- builder: (_) => (_navigationIndex == 0)
- ? Padding(
- padding: const EdgeInsets.fromLTRB(0, 32, 0, 0),
- child: _ItemList(
- selected: selected,
- items: _allItems,
- selectCard: selectCard,
+ },
+ ),
+ body: SlotLayout(
+ config: {
+ Breakpoints.standard: SlotLayout.from(
+ key: const Key('body'),
+ // The conditional here is for navigation screens. The first
+ // screen shows the main screen and every other screen shows
+ // ExamplePage.
+ builder: (_) => (_navigationIndex == 0)
+ ? Padding(
+ padding: const EdgeInsets.fromLTRB(0, 32, 0, 0),
+ child: _ItemList(
+ selected: selected,
+ items: _allItems,
+ selectCard: selectCard,
+ ),
+ )
+ : const _ExamplePage(),
+ ),
+ },
+ ),
+ secondaryBody: _navigationIndex == 0
+ ? SlotLayout(
+ config: {
+ Breakpoints.mediumAndUp: SlotLayout.from(
+ // This overrides the default behavior of the secondaryBody
+ // disappearing as it is animating out.
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ key: const Key('Secondary Body'),
+ builder: (_) => SafeArea(
+ child: _DetailTile(item: _allItems[selected ?? 0]),
),
)
- : const _ExamplePage(),
- ),
- },
- ),
- secondaryBody: _navigationIndex == 0
- ? SlotLayout(
- config: {
- Breakpoints.mediumAndUp: SlotLayout.from(
- // This overrides the default behavior of the secondaryBody
- // disappearing as it is animating out.
- outAnimation: AdaptiveScaffold.stayOnScreen,
- key: const Key('Secondary Body'),
- builder: (_) => SafeArea(
- child: _DetailTile(item: _allItems[selected ?? 0]),
- ),
- )
- },
+ },
+ )
+ : null,
+ bottomNavigation: SlotLayout(
+ config: {
+ Breakpoints.small: SlotLayout.from(
+ key: const Key('bottomNavigation'),
+ // You can define inAnimations or outAnimations to override the
+ // default offset transition.
+ outAnimation: AdaptiveScaffold.topToBottom,
+ builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
+ destinations: destinations,
+ ),
)
- : null,
- bottomNavigation: SlotLayout(
- config: {
- Breakpoints.small: SlotLayout.from(
- key: const Key('bottomNavigation'),
- // You can define inAnimations or outAnimations to override the
- // default offset transition.
- outAnimation: AdaptiveScaffold.topToBottom,
- builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
- destinations: destinations,
- ),
- )
- },
+ },
+ ),
),
),
);
@@ -412,11 +444,9 @@ class _LargeComposeIcon extends StatelessWidget {
child: Column(children: [
Container(
padding: const EdgeInsets.fromLTRB(6, 0, 0, 0),
- // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
- // ignore: prefer_const_constructors
- child: Row(
+ child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: const [
+ children: [
Text(
'REPLY',
style: TextStyle(color: Colors.deepPurple, fontSize: 15),
@@ -444,14 +474,10 @@ class _LargeComposeIcon extends StatelessWidget {
),
width: 200,
height: 50,
- // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
- // ignore: prefer_const_constructors
- child: Padding(
- padding: const EdgeInsets.fromLTRB(16.0, 0, 0, 0),
- // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
- // ignore: prefer_const_constructors
+ child: const Padding(
+ padding: EdgeInsets.fromLTRB(16.0, 0, 0, 0),
child: Row(
- children: const [
+ children: [
Icon(Icons.edit_outlined),
SizedBox(width: 20),
Center(child: Text('Compose')),
diff --git a/packages/flutter_adaptive_scaffold/example/pubspec.yaml b/packages/flutter_adaptive_scaffold/example/pubspec.yaml
index 70ca3041abe6..54e91a84f662 100644
--- a/packages/flutter_adaptive_scaffold/example/pubspec.yaml
+++ b/packages/flutter_adaptive_scaffold/example/pubspec.yaml
@@ -4,8 +4,8 @@ publish_to: 'none'
version: 0.0.1
environment:
- sdk: ">=2.18.0 <4.0.0"
- flutter: ">=3.3.0"
+ sdk: ">=3.0.0 <4.0.0"
+ flutter: ">=3.10.0"
dependencies:
flutter:
diff --git a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart
index 028f47e7cad7..f3485ae370bf 100644
--- a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart
+++ b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart
@@ -488,165 +488,160 @@ class _AdaptiveScaffoldState extends State {
final NavigationRailThemeData navRailTheme =
Theme.of(context).navigationRailTheme;
- return Directionality(
- textDirection: TextDirection.ltr,
- child: Scaffold(
- appBar: widget.drawerBreakpoint.isActive(context) && widget.useDrawer
- ? widget.appBar ?? AppBar()
- : null,
- drawer: widget.drawerBreakpoint.isActive(context) && widget.useDrawer
- ? Drawer(
- child: NavigationRail(
- extended: true,
- leading: widget.leadingExtendedNavRail,
- trailing: widget.trailingNavRail,
- selectedIndex: widget.selectedIndex,
- destinations: widget.destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- onDestinationSelected: widget.onSelectedIndexChange,
- ),
- )
- : null,
- body: AdaptiveLayout(
- bodyOrientation: widget.bodyOrientation,
- bodyRatio: widget.bodyRatio,
- internalAnimations: widget.internalAnimations,
- primaryNavigation: SlotLayout(
- config: {
- widget.mediumBreakpoint: SlotLayout.from(
- key: const Key('primaryNavigation'),
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- width: widget.navigationRailWidth,
- leading: widget.leadingUnextendedNavRail,
- trailing: widget.trailingNavRail,
- selectedIndex: widget.selectedIndex,
- destinations: widget.destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- onDestinationSelected: widget.onSelectedIndexChange,
- backgroundColor: navRailTheme.backgroundColor,
- selectedIconTheme: navRailTheme.selectedIconTheme,
- unselectedIconTheme: navRailTheme.unselectedIconTheme,
- selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
- unSelectedLabelTextStyle:
- navRailTheme.unselectedLabelTextStyle,
- ),
+ return Scaffold(
+ appBar: widget.drawerBreakpoint.isActive(context) && widget.useDrawer
+ ? widget.appBar ?? AppBar()
+ : null,
+ drawer: widget.drawerBreakpoint.isActive(context) && widget.useDrawer
+ ? Drawer(
+ child: NavigationRail(
+ extended: true,
+ leading: widget.leadingExtendedNavRail,
+ trailing: widget.trailingNavRail,
+ selectedIndex: widget.selectedIndex,
+ destinations: widget.destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ onDestinationSelected: widget.onSelectedIndexChange,
),
- widget.largeBreakpoint: SlotLayout.from(
- key: const Key('primaryNavigation1'),
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- width: widget.extendedNavigationRailWidth,
- extended: true,
- leading: widget.leadingExtendedNavRail,
- trailing: widget.trailingNavRail,
- selectedIndex: widget.selectedIndex,
- destinations: widget.destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- onDestinationSelected: widget.onSelectedIndexChange,
- backgroundColor: navRailTheme.backgroundColor,
- selectedIconTheme: navRailTheme.selectedIconTheme,
- unselectedIconTheme: navRailTheme.unselectedIconTheme,
- selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
- unSelectedLabelTextStyle:
- navRailTheme.unselectedLabelTextStyle,
- ),
+ )
+ : null,
+ body: AdaptiveLayout(
+ bodyOrientation: widget.bodyOrientation,
+ bodyRatio: widget.bodyRatio,
+ internalAnimations: widget.internalAnimations,
+ primaryNavigation: SlotLayout(
+ config: {
+ widget.mediumBreakpoint: SlotLayout.from(
+ key: const Key('primaryNavigation'),
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ width: widget.navigationRailWidth,
+ leading: widget.leadingUnextendedNavRail,
+ trailing: widget.trailingNavRail,
+ selectedIndex: widget.selectedIndex,
+ destinations: widget.destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ onDestinationSelected: widget.onSelectedIndexChange,
+ backgroundColor: navRailTheme.backgroundColor,
+ selectedIconTheme: navRailTheme.selectedIconTheme,
+ unselectedIconTheme: navRailTheme.unselectedIconTheme,
+ selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
+ unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
),
- },
- ),
- bottomNavigation:
- !widget.drawerBreakpoint.isActive(context) || !widget.useDrawer
- ? SlotLayout(
- config: {
- widget.smallBreakpoint: SlotLayout.from(
- key: const Key('bottomNavigation'),
- builder: (_) =>
- AdaptiveScaffold.standardBottomNavigationBar(
- currentIndex: widget.selectedIndex,
- destinations: widget.destinations,
- onDestinationSelected: widget.onSelectedIndexChange,
- ),
- ),
- },
- )
- : null,
- body: SlotLayout(
- config: {
- Breakpoints.standard: SlotLayout.from(
- key: const Key('body'),
- inAnimation: AdaptiveScaffold.fadeIn,
- outAnimation: AdaptiveScaffold.fadeOut,
- builder: widget.body,
- ),
- if (widget.smallBody != null)
- widget.smallBreakpoint:
- (widget.smallBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('smallBody'),
- inAnimation: AdaptiveScaffold.fadeIn,
- outAnimation: AdaptiveScaffold.fadeOut,
- builder: widget.smallBody,
- )
- : null,
- if (widget.body != null)
- widget.mediumBreakpoint:
- (widget.body != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('body'),
- inAnimation: AdaptiveScaffold.fadeIn,
- outAnimation: AdaptiveScaffold.fadeOut,
- builder: widget.body,
- )
- : null,
- if (widget.largeBody != null)
- widget.largeBreakpoint:
- (widget.largeBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('largeBody'),
- inAnimation: AdaptiveScaffold.fadeIn,
- outAnimation: AdaptiveScaffold.fadeOut,
- builder: widget.largeBody,
- )
- : null,
- },
- ),
- secondaryBody: SlotLayout(
- config: {
- Breakpoints.standard: SlotLayout.from(
- key: const Key('sBody'),
- outAnimation: AdaptiveScaffold.stayOnScreen,
- builder: widget.secondaryBody,
+ ),
+ widget.largeBreakpoint: SlotLayout.from(
+ key: const Key('primaryNavigation1'),
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ width: widget.extendedNavigationRailWidth,
+ extended: true,
+ leading: widget.leadingExtendedNavRail,
+ trailing: widget.trailingNavRail,
+ selectedIndex: widget.selectedIndex,
+ destinations: widget.destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ onDestinationSelected: widget.onSelectedIndexChange,
+ backgroundColor: navRailTheme.backgroundColor,
+ selectedIconTheme: navRailTheme.selectedIconTheme,
+ unselectedIconTheme: navRailTheme.unselectedIconTheme,
+ selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
+ unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
),
- if (widget.smallSecondaryBody != null)
- widget.smallBreakpoint:
- (widget.smallSecondaryBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('smallSBody'),
- outAnimation: AdaptiveScaffold.stayOnScreen,
- builder: widget.smallSecondaryBody,
- )
- : null,
- if (widget.secondaryBody != null)
- widget.mediumBreakpoint:
- (widget.secondaryBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('sBody'),
- outAnimation: AdaptiveScaffold.stayOnScreen,
- builder: widget.secondaryBody,
- )
- : null,
- if (widget.largeSecondaryBody != null)
- widget.largeBreakpoint:
- (widget.largeSecondaryBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('largeSBody'),
- outAnimation: AdaptiveScaffold.stayOnScreen,
- builder: widget.largeSecondaryBody,
- )
- : null,
- },
- ),
+ ),
+ },
+ ),
+ bottomNavigation:
+ !widget.drawerBreakpoint.isActive(context) || !widget.useDrawer
+ ? SlotLayout(
+ config: {
+ widget.smallBreakpoint: SlotLayout.from(
+ key: const Key('bottomNavigation'),
+ builder: (_) =>
+ AdaptiveScaffold.standardBottomNavigationBar(
+ currentIndex: widget.selectedIndex,
+ destinations: widget.destinations,
+ onDestinationSelected: widget.onSelectedIndexChange,
+ ),
+ ),
+ },
+ )
+ : null,
+ body: SlotLayout(
+ config: {
+ Breakpoints.standard: SlotLayout.from(
+ key: const Key('body'),
+ inAnimation: AdaptiveScaffold.fadeIn,
+ outAnimation: AdaptiveScaffold.fadeOut,
+ builder: widget.body,
+ ),
+ if (widget.smallBody != null)
+ widget.smallBreakpoint:
+ (widget.smallBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('smallBody'),
+ inAnimation: AdaptiveScaffold.fadeIn,
+ outAnimation: AdaptiveScaffold.fadeOut,
+ builder: widget.smallBody,
+ )
+ : null,
+ if (widget.body != null)
+ widget.mediumBreakpoint:
+ (widget.body != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('body'),
+ inAnimation: AdaptiveScaffold.fadeIn,
+ outAnimation: AdaptiveScaffold.fadeOut,
+ builder: widget.body,
+ )
+ : null,
+ if (widget.largeBody != null)
+ widget.largeBreakpoint:
+ (widget.largeBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('largeBody'),
+ inAnimation: AdaptiveScaffold.fadeIn,
+ outAnimation: AdaptiveScaffold.fadeOut,
+ builder: widget.largeBody,
+ )
+ : null,
+ },
+ ),
+ secondaryBody: SlotLayout(
+ config: {
+ Breakpoints.standard: SlotLayout.from(
+ key: const Key('sBody'),
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ builder: widget.secondaryBody,
+ ),
+ if (widget.smallSecondaryBody != null)
+ widget.smallBreakpoint:
+ (widget.smallSecondaryBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('smallSBody'),
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ builder: widget.smallSecondaryBody,
+ )
+ : null,
+ if (widget.secondaryBody != null)
+ widget.mediumBreakpoint:
+ (widget.secondaryBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('sBody'),
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ builder: widget.secondaryBody,
+ )
+ : null,
+ if (widget.largeSecondaryBody != null)
+ widget.largeBreakpoint:
+ (widget.largeSecondaryBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('largeSBody'),
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ builder: widget.largeSecondaryBody,
+ )
+ : null,
+ },
),
),
);
diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml
index d988420a2221..155e08665b41 100644
--- a/packages/flutter_adaptive_scaffold/pubspec.yaml
+++ b/packages/flutter_adaptive_scaffold/pubspec.yaml
@@ -1,12 +1,12 @@
name: flutter_adaptive_scaffold
description: Widgets to easily build adaptive layouts, including navigation elements.
-version: 0.1.4
+version: 0.1.5
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold
environment:
- sdk: ">=2.18.0 <4.0.0"
- flutter: ">=3.3.0"
+ sdk: ">=3.0.0 <4.0.0"
+ flutter: ">=3.10.0"
dependencies:
flutter:
diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart
index 90fb25bffdb0..5a84f4476e41 100644
--- a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart
+++ b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart
@@ -170,9 +170,7 @@ void main() {
expect(begin, findsOneWidget);
expect(end, findsOneWidget);
}
- // TODO(gspencergoog): Remove skip when AnimatedSwitcher fix rolls into stable.
- // https://github.com/flutter/flutter/pull/107476
- }, skip: true);
+ });
testWidgets('slot layout can tolerate rapid changes in breakpoints',
(WidgetTester tester) async {
@@ -191,9 +189,7 @@ void main() {
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
- // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable.
- // https://github.com/flutter/flutter/pull/107476
- }, skip: true);
+ });
// This test reflects the behavior of the internal animations of both the body
// and secondary body and also the navigational items. This is reflected in
@@ -248,9 +244,7 @@ void main() {
expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10));
expect(
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
- // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable.
- // https://github.com/flutter/flutter/pull/107476
- }, skip: true);
+ });
testWidgets('adaptive layout does not animate when animations off',
(WidgetTester tester) async {
@@ -269,9 +263,7 @@ void main() {
expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10));
expect(
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
- // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable.
- // https://github.com/flutter/flutter/pull/107476
- }, skip: true);
+ });
}
class TestBreakpoint0 extends Breakpoint {
diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart
index 325628645522..463dd84a0b3b 100644
--- a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart
+++ b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart
@@ -543,6 +543,43 @@ void main() {
tester.widget(find.byType(NavigationRail));
expect(rail.groupAlignment, equals(groupAlignment));
});
+
+ testWidgets(
+ "doesn't override Directionality",
+ (WidgetTester tester) async {
+ const List destinations = [
+ NavigationDestination(
+ icon: Icon(Icons.home),
+ label: 'Home',
+ ),
+ NavigationDestination(
+ icon: Icon(Icons.account_circle),
+ label: 'Profile',
+ ),
+ ];
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: Directionality(
+ textDirection: TextDirection.rtl,
+ child: AdaptiveScaffold(
+ destinations: destinations,
+ body: (BuildContext context) {
+ return const SizedBox.shrink();
+ },
+ ),
+ ),
+ ),
+ ),
+ );
+
+ final Finder body = find.byKey(const Key('body'));
+ expect(body, findsOneWidget);
+ final TextDirection dir = Directionality.of(body.evaluate().first);
+ expect(dir, TextDirection.rtl);
+ },
+ );
}
/// An empty widget that implements [PreferredSizeWidget] to ensure that
diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md
index 3e6a59396a2e..21a7e338e1ea 100644
--- a/packages/flutter_markdown/CHANGELOG.md
+++ b/packages/flutter_markdown/CHANGELOG.md
@@ -1,3 +1,8 @@
+## NEXT
+
+* Fixes stale ignore: prefer_const_constructors.
+* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
+
## 0.6.15
* Fixes unawaited_futures violations.
diff --git a/packages/flutter_markdown/example/ios/Runner/Info.plist b/packages/flutter_markdown/example/ios/Runner/Info.plist
index 6c83e4da6515..a5b939285a5a 100644
--- a/packages/flutter_markdown/example/ios/Runner/Info.plist
+++ b/packages/flutter_markdown/example/ios/Runner/Info.plist
@@ -39,8 +39,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/flutter_markdown/lib/src/_functions_io.dart b/packages/flutter_markdown/lib/src/_functions_io.dart
index 7c35ba485333..b3fa99ea63e6 100644
--- a/packages/flutter_markdown/lib/src/_functions_io.dart
+++ b/packages/flutter_markdown/lib/src/_functions_io.dart
@@ -66,7 +66,8 @@ final MarkdownStyleSheet Function(BuildContext, MarkdownStyleSheetBaseTheme?)
}
return result.copyWith(
- textScaleFactor: MediaQuery.textScaleFactorOf(context),
+ textScaleFactor:
+ MediaQuery.textScaleFactorOf(context), // ignore: deprecated_member_use
);
};
diff --git a/packages/flutter_markdown/lib/src/_functions_web.dart b/packages/flutter_markdown/lib/src/_functions_web.dart
index a58a9cea37d6..828388613a53 100644
--- a/packages/flutter_markdown/lib/src/_functions_web.dart
+++ b/packages/flutter_markdown/lib/src/_functions_web.dart
@@ -68,7 +68,8 @@ final MarkdownStyleSheet Function(BuildContext, MarkdownStyleSheetBaseTheme?)
}
return result.copyWith(
- textScaleFactor: MediaQuery.textScaleFactorOf(context),
+ textScaleFactor:
+ MediaQuery.textScaleFactorOf(context), // ignore: deprecated_member_use
);
};
diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart
index 475c510be98f..f32ec0a19f03 100644
--- a/packages/flutter_markdown/lib/src/builder.dart
+++ b/packages/flutter_markdown/lib/src/builder.dart
@@ -829,6 +829,7 @@ class MarkdownBuilder implements md.NodeVisitor {
if (selectable) {
return SelectableText.rich(
text!,
+ // ignore: deprecated_member_use
textScaleFactor: styleSheet.textScaleFactor,
textAlign: textAlign ?? TextAlign.start,
onTap: onTapText,
@@ -837,6 +838,7 @@ class MarkdownBuilder implements md.NodeVisitor {
} else {
return RichText(
text: text!,
+ // ignore: deprecated_member_use
textScaleFactor: styleSheet.textScaleFactor!,
textAlign: textAlign ?? TextAlign.start,
key: k,
diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml
index 74b886c1c75d..525222b9c282 100644
--- a/packages/flutter_markdown/pubspec.yaml
+++ b/packages/flutter_markdown/pubspec.yaml
@@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+
version: 0.6.15
environment:
- sdk: ">=2.18.0 <4.0.0"
- flutter: ">=3.3.0"
+ sdk: ">=3.0.0 <4.0.0"
+ flutter: ">=3.10.0"
dependencies:
flutter:
diff --git a/packages/flutter_markdown/test/list_test.dart b/packages/flutter_markdown/test/list_test.dart
index 0703dfd44483..4a587c50a93f 100644
--- a/packages/flutter_markdown/test/list_test.dart
+++ b/packages/flutter_markdown/test/list_test.dart
@@ -194,10 +194,8 @@ void defineTests() {
await tester.pumpWidget(
boilerplate(
- // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
- // ignore: prefer_const_constructors
- Column(
- children: const [
+ const Column(
+ children: [
MarkdownBody(fitContent: false, data: data),
],
),
@@ -219,10 +217,8 @@ void defineTests() {
await tester.pumpWidget(
boilerplate(
- // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
- // ignore: prefer_const_constructors
- Column(
- children: const [
+ const Column(
+ children: [
MarkdownBody(data: data),
],
),
diff --git a/packages/flutter_markdown/test/markdown_body_shrink_wrap_test.dart b/packages/flutter_markdown/test/markdown_body_shrink_wrap_test.dart
index 9dc611be70a6..a5ae86953921 100644
--- a/packages/flutter_markdown/test/markdown_body_shrink_wrap_test.dart
+++ b/packages/flutter_markdown/test/markdown_body_shrink_wrap_test.dart
@@ -16,10 +16,8 @@ void defineTests() {
'Then it wraps its content',
(WidgetTester tester) async {
await tester.pumpWidget(boilerplate(
- // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
- // ignore: prefer_const_constructors
- Stack(
- children: const [
+ const Stack(
+ children: [
Text('shrinkWrap=true'),
Align(
alignment: Alignment.bottomCenter,
@@ -48,10 +46,8 @@ void defineTests() {
'Then it expands to the maximum allowed height',
(WidgetTester tester) async {
await tester.pumpWidget(boilerplate(
- // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
- // ignore: prefer_const_constructors
- Stack(
- children: const [
+ const Stack(
+ children: [
Text('shrinkWrap=false test'),
Align(
alignment: Alignment.bottomCenter,
diff --git a/packages/flutter_markdown/test/text_scale_factor_test.dart b/packages/flutter_markdown/test/text_scale_factor_test.dart
index 3710b3e0a62e..2f3138a94aef 100644
--- a/packages/flutter_markdown/test/text_scale_factor_test.dart
+++ b/packages/flutter_markdown/test/text_scale_factor_test.dart
@@ -25,7 +25,7 @@ void defineTests() {
);
final RichText richText = tester.widget(find.byType(RichText));
- expect(richText.textScaleFactor, 2.0);
+ expect(richText.textScaleFactor, 2.0); // ignore: deprecated_member_use
},
);
@@ -36,6 +36,7 @@ void defineTests() {
await tester.pumpWidget(
boilerplate(
const MediaQuery(
+ // ignore: deprecated_member_use
data: MediaQueryData(textScaleFactor: 2.0),
child: MarkdownBody(
data: data,
@@ -45,7 +46,7 @@ void defineTests() {
);
final RichText richText = tester.widget(find.byType(RichText));
- expect(richText.textScaleFactor, 2.0);
+ expect(richText.textScaleFactor, 2.0); // ignore: deprecated_member_use
},
);
@@ -56,6 +57,7 @@ void defineTests() {
await tester.pumpWidget(
boilerplate(
const MediaQuery(
+ // ignore: deprecated_member_use
data: MediaQueryData(textScaleFactor: 2.0),
child: MarkdownBody(
data: data,
@@ -67,6 +69,7 @@ void defineTests() {
final SelectableText selectableText =
tester.widget(find.byType(SelectableText));
+ // ignore: deprecated_member_use
expect(selectableText.textScaleFactor, 2.0);
},
);
diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index 0bba2a21f2c7..b0df45c5fe69 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,24 @@
+## 8.0.5
+
+- Fixes a bug that GoRouterState in top level redirect doesn't contain complete data.
+
+## 8.0.4
+
+- Updates documentations around `GoRouter.of`, `GoRouter.maybeOf`, and `BuildContext` extension.
+
+## 8.0.3
+
+- Makes namedLocation and route name related APIs case sensitive.
+
+## 8.0.2
+
+- Fixes a bug in `debugLogDiagnostics` to support StatefulShellRoute.
+
+## 8.0.1
+
+- Fixes a link for an example in `path` documentation.
+ documentation.
+
## 8.0.0
- **BREAKING CHANGE**:
diff --git a/packages/go_router/example/ios/Runner/Info.plist b/packages/go_router/example/ios/Runner/Info.plist
index 4f68a2cee180..677cf7bb8b0e 100644
--- a/packages/go_router/example/ios/Runner/Info.plist
+++ b/packages/go_router/example/ios/Runner/Info.plist
@@ -39,8 +39,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/go_router/lib/src/builder.dart b/packages/go_router/lib/src/builder.dart
index d68958cbaa79..c98f2f8bd123 100644
--- a/packages/go_router/lib/src/builder.dart
+++ b/packages/go_router/lib/src/builder.dart
@@ -145,8 +145,7 @@ class RouteBuilder {
if (matchList.isError) {
keyToPage = , List>>{
navigatorKey: >[
- _buildErrorPage(
- context, _buildErrorState(matchList.error!, matchList.uri)),
+ _buildErrorPage(context, _buildErrorState(matchList)),
]
};
} else {
@@ -325,8 +324,7 @@ class RouteBuilder {
if (match is ImperativeRouteMatch) {
effectiveMatchList = match.matches;
if (effectiveMatchList.isError) {
- return _buildErrorState(
- effectiveMatchList.error!, effectiveMatchList.uri);
+ return _buildErrorState(effectiveMatchList);
}
} else {
effectiveMatchList = matchList;
@@ -491,19 +489,18 @@ class RouteBuilder {
child: child,
);
- GoRouterState _buildErrorState(
- Exception error,
- Uri uri,
- ) {
- final String location = uri.toString();
+ GoRouterState _buildErrorState(RouteMatchList matchList) {
+ final String location = matchList.uri.toString();
+ assert(matchList.isError);
return GoRouterState(
configuration,
location: location,
- matchedLocation: uri.path,
- name: null,
- queryParameters: uri.queryParameters,
- queryParametersAll: uri.queryParametersAll,
- error: error,
+ matchedLocation: matchList.uri.path,
+ fullPath: matchList.fullPath,
+ pathParameters: matchList.pathParameters,
+ queryParameters: matchList.uri.queryParameters,
+ queryParametersAll: matchList.uri.queryParametersAll,
+ error: matchList.error,
pageKey: ValueKey('$location(error)'),
);
}
diff --git a/packages/go_router/lib/src/configuration.dart b/packages/go_router/lib/src/configuration.dart
index f2e9d84617d8..865007d76597 100644
--- a/packages/go_router/lib/src/configuration.dart
+++ b/packages/go_router/lib/src/configuration.dart
@@ -221,9 +221,8 @@ class RouteConfiguration {
'${queryParameters.isEmpty ? '' : ', queryParameters: $queryParameters'}');
return true;
}());
- final String keyName = name.toLowerCase();
- assert(_nameToPath.containsKey(keyName), 'unknown route name: $name');
- final String path = _nameToPath[keyName]!;
+ assert(_nameToPath.containsKey(name), 'unknown route name: $name');
+ final String path = _nameToPath[name]!;
assert(() {
// Check that all required params are present
final List paramNames = [];
@@ -415,9 +414,10 @@ class RouteConfiguration {
GoRouterState(
this,
location: prevLocation,
- name: null,
// No name available at the top level trim the query params off the
// sub-location to match route.redirect
+ fullPath: prevMatchList.fullPath,
+ pathParameters: prevMatchList.pathParameters,
matchedLocation: prevMatchList.uri.path,
queryParameters: prevMatchList.uri.queryParameters,
queryParametersAll: prevMatchList.uri.queryParametersAll,
@@ -552,7 +552,7 @@ class RouteConfiguration {
final String fullPath = concatenatePaths(parentFullpath, route.path);
sb.writeln(' => ${''.padLeft(depth * 2)}$fullPath');
_debugFullPathsFor(route.routes, fullPath, depth + 1, sb);
- } else if (route is ShellRoute) {
+ } else if (route is ShellRouteBase) {
_debugFullPathsFor(route.routes, parentFullpath, depth, sb);
}
}
@@ -564,7 +564,7 @@ class RouteConfiguration {
final String fullPath = concatenatePaths(parentFullPath, route.path);
if (route.name != null) {
- final String name = route.name!.toLowerCase();
+ final String name = route.name!;
assert(
!_nameToPath.containsKey(name),
'duplication fullpaths for name '
diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart
index c4ff4ad6a95a..58e3c3fea7c0 100644
--- a/packages/go_router/lib/src/misc/extensions.dart
+++ b/packages/go_router/lib/src/misc/extensions.dart
@@ -10,6 +10,8 @@ import '../router.dart';
/// context.go('/');
extension GoRouterHelper on BuildContext {
/// Get a location from route name and parameters.
+ ///
+ /// This method can't be called during redirects.
String namedLocation(
String name, {
Map pathParameters = const {},
diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart
index 8983a7894e9c..ba656326e328 100644
--- a/packages/go_router/lib/src/route.dart
+++ b/packages/go_router/lib/src/route.dart
@@ -208,7 +208,7 @@ class GoRoute extends RouteBase {
/// The query parameter are also capture during the route parsing and stored
/// in [GoRouterState].
///
- /// See [Query parameters and path parameters](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/sub_routes.dart)
+ /// See [Query parameters and path parameters](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/path_and_query_parameters.dart)
/// to learn more about parameters.
final String path;
diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart
index eac57c225581..15ceea634401 100644
--- a/packages/go_router/lib/src/router.dart
+++ b/packages/go_router/lib/src/router.dart
@@ -466,6 +466,8 @@ class GoRouter extends ChangeNotifier implements RouterConfig {
}
/// Find the current GoRouter in the widget tree.
+ ///
+ /// This method throws when it is called during redirects.
static GoRouter of(BuildContext context) {
final InheritedGoRouter? inherited =
context.dependOnInheritedWidgetOfExactType();
@@ -474,6 +476,8 @@ class GoRouter extends ChangeNotifier implements RouterConfig {
}
/// The current GoRouter in the widget tree, if any.
+ ///
+ /// This method returns null when it is called during redirects.
static GoRouter? maybeOf(BuildContext context) {
final InheritedGoRouter? inherited =
context.dependOnInheritedWidgetOfExactType();
diff --git a/packages/go_router/lib/src/state.dart b/packages/go_router/lib/src/state.dart
index 9c634eddbeea..13ee9add91e2 100644
--- a/packages/go_router/lib/src/state.dart
+++ b/packages/go_router/lib/src/state.dart
@@ -17,12 +17,12 @@ class GoRouterState {
this._configuration, {
required this.location,
required this.matchedLocation,
- required this.name,
+ this.name,
this.path,
- this.fullPath,
- this.pathParameters = const {},
- this.queryParameters = const {},
- this.queryParametersAll = const >{},
+ required this.fullPath,
+ required this.pathParameters,
+ required this.queryParameters,
+ required this.queryParametersAll,
this.extra,
this.error,
required this.pageKey,
@@ -42,16 +42,24 @@ class GoRouterState {
/// matchedLocation = /family/f2
final String matchedLocation;
- /// The optional name of the route.
+ /// The optional name of the route associated with this app.
+ ///
+ /// This can be null for GoRouterState pass into top level redirect.
final String? name;
- /// The path to this sub-route, e.g. family/:fid
+ /// The path of the route associated with this app. e.g. family/:fid
+ ///
+ /// This can be null for GoRouterState pass into top level redirect.
final String? path;
/// The full path to this sub-route, e.g. /family/:fid
+ ///
+ /// For top level redirect, this is the entire path that matches the location.
+ /// It can be empty if go router can't find a match. In that case, the [error]
+ /// contains more information.
final String? fullPath;
- /// The parameters for this sub-route, e.g. {'fid': 'f2'}
+ /// The parameters for this match, e.g. {'fid': 'f2'}
final Map pathParameters;
/// The query parameters for the location, e.g. {'from': '/family/f2'}
@@ -64,7 +72,7 @@ class GoRouterState {
/// An extra object to pass along with the navigation.
final Object? extra;
- /// The error associated with this sub-route.
+ /// The error associated with this match.
final Exception? error;
/// A unique string key for this sub-route.
@@ -129,8 +137,6 @@ class GoRouterState {
/// Get a location from route name and parameters.
/// This is useful for redirecting to a named location.
- // TODO(chunhtai): remove this method when go_router can provide a way to
- // look up named location during redirect.
String namedLocation(
String name, {
Map pathParameters = const {},
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index b71ea714404c..db489edff28b 100644
--- a/packages/go_router/pubspec.yaml
+++ b/packages/go_router/pubspec.yaml
@@ -1,7 +1,7 @@
name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more
-version: 8.0.0
+version: 8.0.5
repository: https://github.com/flutter/packages/tree/main/packages/go_router
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
diff --git a/packages/go_router/test/configuration_test.dart b/packages/go_router/test/configuration_test.dart
index ac6ff0e398d8..ac4d1ed848fa 100644
--- a/packages/go_router/test/configuration_test.dart
+++ b/packages/go_router/test/configuration_test.dart
@@ -1037,6 +1037,33 @@ void main() {
),
],
),
+ GoRoute(
+ path: '/g',
+ builder: _mockScreenBuilder,
+ routes: [
+ StatefulShellRoute.indexedStack(
+ builder: _mockIndexedStackShellBuilder,
+ branches: [
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: 'h',
+ builder: _mockScreenBuilder,
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: 'i',
+ builder: _mockScreenBuilder,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
],
redirectLimit: 10,
topRedirect: (BuildContext context, GoRouterState state) {
@@ -1049,7 +1076,10 @@ void main() {
' => /a/c\n'
' => /d\n'
' => /d/e\n'
- ' => /d/e/f\n',
+ ' => /d/e/f\n'
+ ' => /g\n'
+ ' => /g/h\n'
+ ' => /g/i\n',
);
},
);
@@ -1069,3 +1099,7 @@ Widget _mockScreenBuilder(BuildContext context, GoRouterState state) =>
Widget _mockShellBuilder(
BuildContext context, GoRouterState state, Widget child) =>
child;
+
+Widget _mockIndexedStackShellBuilder(BuildContext context, GoRouterState state,
+ StatefulNavigationShell shell) =>
+ shell;
diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart
index b3b46660e64d..2ebb4afca774 100644
--- a/packages/go_router/test/go_router_test.dart
+++ b/packages/go_router/test/go_router_test.dart
@@ -1493,8 +1493,7 @@ void main() {
}, throwsA(isAssertionError));
});
- testWidgets('match case insensitive w/ params',
- (WidgetTester tester) async {
+ testWidgets('cannot match case insensitive', (WidgetTester tester) async {
final List routes = [
GoRoute(
name: 'home',
@@ -1524,8 +1523,15 @@ void main() {
];
final GoRouter router = await createRouter(routes, tester);
- router.goNamed('person',
- pathParameters: {'fid': 'f2', 'pid': 'p1'});
+ expect(
+ () {
+ router.goNamed(
+ 'person',
+ pathParameters: {'fid': 'f2', 'pid': 'p1'},
+ );
+ },
+ throwsAssertionError,
+ );
});
testWidgets('too few params', (WidgetTester tester) async {
@@ -2016,7 +2022,7 @@ void main() {
expect(Uri.parse(state.location).queryParameters, isNotEmpty);
expect(Uri.parse(state.matchedLocation).queryParameters, isEmpty);
expect(state.path, isNull);
- expect(state.fullPath, isNull);
+ expect(state.fullPath, '/login');
expect(state.pathParameters.length, 0);
expect(state.queryParameters.length, 1);
expect(state.queryParameters['from'], '/');
@@ -2030,6 +2036,40 @@ void main() {
expect(find.byType(LoginScreen), findsOneWidget);
});
+ testWidgets('top-level redirect state contains path parameters',
+ (WidgetTester tester) async {
+ final List routes = [
+ GoRoute(
+ path: '/',
+ builder: (BuildContext context, GoRouterState state) =>
+ const DummyScreen(),
+ routes: [
+ GoRoute(
+ path: ':id',
+ builder: (BuildContext context, GoRouterState state) =>
+ const DummyScreen(),
+ ),
+ ]),
+ ];
+
+ final GoRouter router = await createRouter(
+ routes,
+ tester,
+ initialLocation: '/123',
+ redirect: (BuildContext context, GoRouterState state) {
+ expect(state.path, isNull);
+ expect(state.fullPath, '/:id');
+ expect(state.pathParameters.length, 1);
+ expect(state.pathParameters['id'], '123');
+ return null;
+ },
+ );
+
+ final List matches =
+ router.routerDelegate.currentConfiguration.matches;
+ expect(matches, hasLength(2));
+ });
+
testWidgets('route-level redirect state', (WidgetTester tester) async {
const String loc = '/book/0';
final List routes = [
diff --git a/packages/go_router/test/name_case_test.dart b/packages/go_router/test/name_case_test.dart
new file mode 100644
index 000000000000..6e3f067197fe
--- /dev/null
+++ b/packages/go_router/test/name_case_test.dart
@@ -0,0 +1,66 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:go_router/go_router.dart';
+
+void main() {
+ testWidgets(
+ 'Route names are case sensitive',
+ (WidgetTester tester) async {
+ // config router with 2 routes with the same name but different case (Name, name)
+ final GoRouter router = GoRouter(
+ routes: [
+ GoRoute(
+ path: '/',
+ name: 'Name',
+ builder: (_, __) => const ScreenA(),
+ ),
+ GoRoute(
+ path: '/path',
+ name: 'name',
+ builder: (_, __) => const ScreenB(),
+ ),
+ ],
+ );
+
+ // run MaterialApp, initial screen path is '/' -> ScreenA
+ await tester.pumpWidget(
+ MaterialApp.router(
+ routerConfig: router,
+ title: 'GoRouter Testcase',
+ ),
+ );
+
+ // go to ScreenB
+ router.goNamed('name');
+ await tester.pumpAndSettle();
+ expect(find.byType(ScreenB), findsOneWidget);
+
+ // go to ScreenA
+ router.goNamed('Name');
+ await tester.pumpAndSettle();
+ expect(find.byType(ScreenA), findsOneWidget);
+ },
+ );
+}
+
+class ScreenA extends StatelessWidget {
+ const ScreenA({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container();
+ }
+}
+
+class ScreenB extends StatelessWidget {
+ const ScreenB({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container();
+ }
+}
diff --git a/packages/go_router/test/parser_test.dart b/packages/go_router/test/parser_test.dart
index ee4279b51576..253790f01163 100644
--- a/packages/go_router/test/parser_test.dart
+++ b/packages/go_router/test/parser_test.dart
@@ -118,11 +118,8 @@ void main() {
);
expect(configuration.namedLocation('lowercase'), '/abc');
- expect(configuration.namedLocation('LOWERCASE'), '/abc');
expect(configuration.namedLocation('camelCase'), '/efg');
- expect(configuration.namedLocation('camelcase'), '/efg');
expect(configuration.namedLocation('snake_case'), '/hij');
- expect(configuration.namedLocation('SNAKE_CASE'), '/hij');
// With query parameters
expect(configuration.namedLocation('lowercase'), '/abc');
diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md
index 81a4e7db62f0..ce6210d043fd 100644
--- a/packages/go_router_builder/CHANGELOG.md
+++ b/packages/go_router_builder/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.1.1
+
+* Fixes a bug that the required/positional parameters are not added to query parameters correctly.
+
## 2.1.0
* Supports required/positional parameters that are not in the path.
diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart
index 3e53cb1c427e..693eb4a42cf3 100644
--- a/packages/go_router_builder/lib/src/route_config.dart
+++ b/packages/go_router_builder/lib/src/route_config.dart
@@ -441,7 +441,7 @@ GoRouteData.\$route(
late final List _ctorParams =
_ctor.parameters.where((ParameterElement element) {
- if (element.isRequired && !element.isExtraField) {
+ if (_pathParams.contains(element.name)) {
return true;
}
return false;
@@ -449,7 +449,7 @@ GoRouteData.\$route(
late final List _ctorQueryParams = _ctor.parameters
.where((ParameterElement element) =>
- element.isOptional && !element.isExtraField)
+ !_pathParams.contains(element.name) && !element.isExtraField)
.toList();
ConstructorElement get _ctor {
diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml
index 027b79ea2c6f..9f213c1508d2 100644
--- a/packages/go_router_builder/pubspec.yaml
+++ b/packages/go_router_builder/pubspec.yaml
@@ -2,7 +2,7 @@ name: go_router_builder
description: >-
A builder that supports generated strongly-typed route helpers for
package:go_router
-version: 2.1.0
+version: 2.1.1
repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22
diff --git a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart
index 016d2c86518a..b27b1ea91002 100644
--- a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart
+++ b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart
@@ -68,6 +68,9 @@ extension $NullableRequiredParamNotInPathExtension
String get location => GoRouteData.$location(
'bob',
+ queryParams: {
+ if (id != null) 'id': id!.toString(),
+ },
);
void go(BuildContext context) => context.go(location);
@@ -108,6 +111,9 @@ extension $NonNullableRequiredParamNotInPathExtension
String get location => GoRouteData.$location(
'bob',
+ queryParams: {
+ 'id': id.toString(),
+ },
);
void go(BuildContext context) => context.go(location);
diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index a0b461f10eb1..6edda3e08049 100644
--- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 2.3.1
+
+* Fixes a regression from 2.2.8 that could cause incorrect handling of a
+ rapid series of map object updates.
+* Fixes stale ignore: prefer_const_constructors.
+* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
+
## 2.3.0
* Endorses [`google_maps_flutter_web`](https://pub.dev/packages/google_maps_flutter_web)
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Info.plist b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Info.plist
index d6b389f16721..6783ca935f1d 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Info.plist
+++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Info.plist
@@ -45,8 +45,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml
index 9c0ab999ddbc..6b3d16007c4b 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml
@@ -3,8 +3,8 @@ description: Demonstrates how to use the google_maps_flutter plugin.
publish_to: none
environment:
- sdk: ">=2.18.0 <4.0.0"
- flutter: ">=3.3.0"
+ sdk: ">=3.0.0 <4.0.0"
+ flutter: ">=3.10.0"
dependencies:
cupertino_icons: ^1.0.5
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
index e1b710c307a6..08c2286527fb 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
@@ -360,41 +360,41 @@ class _GoogleMapState extends State {
return;
}
final GoogleMapController controller = await _controller.future;
- await controller._updateMapConfiguration(updates);
+ unawaited(controller._updateMapConfiguration(updates));
_mapConfiguration = newConfig;
}
Future _updateMarkers() async {
final GoogleMapController controller = await _controller.future;
- await controller._updateMarkers(
- MarkerUpdates.from(_markers.values.toSet(), widget.markers));
+ unawaited(controller._updateMarkers(
+ MarkerUpdates.from(_markers.values.toSet(), widget.markers)));
_markers = keyByMarkerId(widget.markers);
}
Future _updatePolygons() async {
final GoogleMapController controller = await _controller.future;
- await controller._updatePolygons(
- PolygonUpdates.from(_polygons.values.toSet(), widget.polygons));
+ unawaited(controller._updatePolygons(
+ PolygonUpdates.from(_polygons.values.toSet(), widget.polygons)));
_polygons = keyByPolygonId(widget.polygons);
}
Future _updatePolylines() async {
final GoogleMapController controller = await _controller.future;
- await controller._updatePolylines(
- PolylineUpdates.from(_polylines.values.toSet(), widget.polylines));
+ unawaited(controller._updatePolylines(
+ PolylineUpdates.from(_polylines.values.toSet(), widget.polylines)));
_polylines = keyByPolylineId(widget.polylines);
}
Future _updateCircles() async {
final GoogleMapController controller = await _controller.future;
- await controller._updateCircles(
- CircleUpdates.from(_circles.values.toSet(), widget.circles));
+ unawaited(controller._updateCircles(
+ CircleUpdates.from(_circles.values.toSet(), widget.circles)));
_circles = keyByCircleId(widget.circles);
}
Future _updateTileOverlays() async {
final GoogleMapController controller = await _controller.future;
- await controller._updateTileOverlays(widget.tileOverlays);
+ unawaited(controller._updateTileOverlays(widget.tileOverlays));
}
Future onPlatformViewCreated(int id) async {
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index d1e577027b37..e56ff513e7a4 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -2,11 +2,11 @@ name: google_maps_flutter
description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
-version: 2.3.0
+version: 2.3.1
environment:
- sdk: ">=2.18.0 <4.0.0"
- flutter: ">=3.3.0"
+ sdk: ">=3.0.0 <4.0.0"
+ flutter: ">=3.10.0"
flutter:
plugin:
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart
index 459e16b60c42..f94caf1f5837 100644
--- a/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart
@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
-import 'fake_maps_controllers.dart';
+import 'fake_google_maps_flutter_platform.dart';
Widget _mapWithCircles(Set circles) {
return Directionality(
@@ -20,36 +20,24 @@ Widget _mapWithCircles(Set circles) {
}
void main() {
- TestWidgetsFlutterBinding.ensureInitialized();
-
- final FakePlatformViewsController fakePlatformViewsController =
- FakePlatformViewsController();
-
- setUpAll(() {
- _ambiguate(TestDefaultBinaryMessengerBinding.instance)!
- .defaultBinaryMessenger
- .setMockMethodCallHandler(
- SystemChannels.platform_views,
- fakePlatformViewsController.fakePlatformViewsMethodHandler,
- );
- });
+ late FakeGoogleMapsFlutterPlatform platform;
setUp(() {
- fakePlatformViewsController.reset();
+ platform = FakeGoogleMapsFlutterPlatform();
+ GoogleMapsFlutterPlatform.instance = platform;
});
testWidgets('Initializing a circle', (WidgetTester tester) async {
const Circle c1 = Circle(circleId: CircleId('circle_1'));
await tester.pumpWidget(_mapWithCircles({c1}));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
- expect(platformGoogleMap.circlesToAdd.length, 1);
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
+ expect(map.circleUpdates.last.circlesToAdd.length, 1);
- final Circle initializedCircle = platformGoogleMap.circlesToAdd.first;
+ final Circle initializedCircle = map.circleUpdates.last.circlesToAdd.first;
expect(initializedCircle, equals(c1));
- expect(platformGoogleMap.circleIdsToRemove.isEmpty, true);
- expect(platformGoogleMap.circlesToChange.isEmpty, true);
+ expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToChange.isEmpty, true);
});
testWidgets('Adding a circle', (WidgetTester tester) async {
@@ -59,16 +47,15 @@ void main() {
await tester.pumpWidget(_mapWithCircles({c1}));
await tester.pumpWidget(_mapWithCircles({c1, c2}));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
- expect(platformGoogleMap.circlesToAdd.length, 1);
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
+ expect(map.circleUpdates.last.circlesToAdd.length, 1);
- final Circle addedCircle = platformGoogleMap.circlesToAdd.first;
+ final Circle addedCircle = map.circleUpdates.last.circlesToAdd.first;
expect(addedCircle, equals(c2));
- expect(platformGoogleMap.circleIdsToRemove.isEmpty, true);
+ expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true);
- expect(platformGoogleMap.circlesToChange.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToChange.isEmpty, true);
});
testWidgets('Removing a circle', (WidgetTester tester) async {
@@ -77,13 +64,12 @@ void main() {
await tester.pumpWidget(_mapWithCircles({c1}));
await tester.pumpWidget(_mapWithCircles({}));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
- expect(platformGoogleMap.circleIdsToRemove.length, 1);
- expect(platformGoogleMap.circleIdsToRemove.first, equals(c1.circleId));
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
+ expect(map.circleUpdates.last.circleIdsToRemove.length, 1);
+ expect(map.circleUpdates.last.circleIdsToRemove.first, equals(c1.circleId));
- expect(platformGoogleMap.circlesToChange.isEmpty, true);
- expect(platformGoogleMap.circlesToAdd.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToChange.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToAdd.isEmpty, true);
});
testWidgets('Updating a circle', (WidgetTester tester) async {
@@ -93,13 +79,12 @@ void main() {
await tester.pumpWidget(_mapWithCircles({c1}));
await tester.pumpWidget(_mapWithCircles({c2}));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
- expect(platformGoogleMap.circlesToChange.length, 1);
- expect(platformGoogleMap.circlesToChange.first, equals(c2));
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
+ expect(map.circleUpdates.last.circlesToChange.length, 1);
+ expect(map.circleUpdates.last.circlesToChange.first, equals(c2));
- expect(platformGoogleMap.circleIdsToRemove.isEmpty, true);
- expect(platformGoogleMap.circlesToAdd.isEmpty, true);
+ expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToAdd.isEmpty, true);
});
testWidgets('Updating a circle', (WidgetTester tester) async {
@@ -109,11 +94,10 @@ void main() {
await tester.pumpWidget(_mapWithCircles({c1}));
await tester.pumpWidget(_mapWithCircles({c2}));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
- expect(platformGoogleMap.circlesToChange.length, 1);
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
+ expect(map.circleUpdates.last.circlesToChange.length, 1);
- final Circle update = platformGoogleMap.circlesToChange.first;
+ final Circle update = map.circleUpdates.last.circlesToChange.first;
expect(update, equals(c2));
expect(update.radius, 10);
});
@@ -129,12 +113,11 @@ void main() {
await tester.pumpWidget(_mapWithCircles(prev));
await tester.pumpWidget(_mapWithCircles(cur));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
- expect(platformGoogleMap.circlesToChange, cur);
- expect(platformGoogleMap.circleIdsToRemove.isEmpty, true);
- expect(platformGoogleMap.circlesToAdd.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToChange, cur);
+ expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToAdd.isEmpty, true);
});
testWidgets('Multi Update', (WidgetTester tester) async {
@@ -150,16 +133,15 @@ void main() {
await tester.pumpWidget(_mapWithCircles(prev));
await tester.pumpWidget(_mapWithCircles(cur));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
- expect(platformGoogleMap.circlesToChange.length, 1);
- expect(platformGoogleMap.circlesToAdd.length, 1);
- expect(platformGoogleMap.circleIdsToRemove.length, 1);
+ expect(map.circleUpdates.last.circlesToChange.length, 1);
+ expect(map.circleUpdates.last.circlesToAdd.length, 1);
+ expect(map.circleUpdates.last.circleIdsToRemove.length, 1);
- expect(platformGoogleMap.circlesToChange.first, equals(c2));
- expect(platformGoogleMap.circlesToAdd.first, equals(c1));
- expect(platformGoogleMap.circleIdsToRemove.first, equals(c3.circleId));
+ expect(map.circleUpdates.last.circlesToChange.first, equals(c2));
+ expect(map.circleUpdates.last.circlesToAdd.first, equals(c1));
+ expect(map.circleUpdates.last.circleIdsToRemove.first, equals(c3.circleId));
});
testWidgets('Partial Update', (WidgetTester tester) async {
@@ -173,12 +155,11 @@ void main() {
await tester.pumpWidget(_mapWithCircles(prev));
await tester.pumpWidget(_mapWithCircles(cur));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
- expect(platformGoogleMap.circlesToChange, {c3});
- expect(platformGoogleMap.circleIdsToRemove.isEmpty, true);
- expect(platformGoogleMap.circlesToAdd.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToChange, {c3});
+ expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToAdd.isEmpty, true);
});
testWidgets('Update non platform related attr', (WidgetTester tester) async {
@@ -190,17 +171,42 @@ void main() {
await tester.pumpWidget(_mapWithCircles(prev));
await tester.pumpWidget(_mapWithCircles(cur));
- final FakePlatformGoogleMap platformGoogleMap =
- fakePlatformViewsController.lastCreatedView!;
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
- expect(platformGoogleMap.circlesToChange.isEmpty, true);
- expect(platformGoogleMap.circleIdsToRemove.isEmpty, true);
- expect(platformGoogleMap.circlesToAdd.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToChange.isEmpty, true);
+ expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true);
+ expect(map.circleUpdates.last.circlesToAdd.isEmpty, true);
});
-}
-/// This allows a value of type T or T? to be treated as a value of type T?.
-///
-/// We use this so that APIs that have become non-nullable can still be used
-/// with `!` and `?` on the stable branch.
-T? _ambiguate(T? value) => value;
+ testWidgets('multi-update with delays', (WidgetTester tester) async {
+ platform.simulatePlatformDelay = true;
+
+ const Circle c1 = Circle(circleId: CircleId('circle_1'));
+ const Circle c2 = Circle(circleId: CircleId('circle_2'));
+ const Circle c3 = Circle(circleId: CircleId('circle_3'), radius: 1);
+ const Circle c3updated = Circle(circleId: CircleId('circle_3'), radius: 10);
+
+ // First remove one and add another, then update the new one.
+ await tester.pumpWidget(_mapWithCircles({c1, c2}));
+ await tester.pumpWidget(_mapWithCircles({c1, c3}));
+ await tester.pumpWidget(_mapWithCircles({c1, c3updated}));
+
+ final PlatformMapStateRecorder map = platform.lastCreatedMap;
+
+ expect(map.circleUpdates.length, 3);
+
+ expect(map.circleUpdates[0].circlesToChange.isEmpty, true);
+ expect(map.circleUpdates[0].circlesToAdd, {c1, c2});
+ expect(map.circleUpdates[0].circleIdsToRemove.isEmpty, true);
+
+ expect(map.circleUpdates[1].circlesToChange.isEmpty, true);
+ expect(map.circleUpdates[1].circlesToAdd, {c3});
+ expect(map.circleUpdates[1].circleIdsToRemove, {c2.circleId});
+
+ expect(map.circleUpdates[2].circlesToChange, {c3updated});
+ expect(map.circleUpdates[2].circlesToAdd.isEmpty, true);
+ expect(map.circleUpdates[2].circleIdsToRemove.isEmpty, true);
+
+ await tester.pumpAndSettle();
+ });
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart
new file mode 100644
index 000000000000..22447ba5ecad
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart
@@ -0,0 +1,303 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'package:stream_transform/stream_transform.dart';
+
+// A dummy implementation of the platform interface for tests.
+class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform {
+ FakeGoogleMapsFlutterPlatform();
+
+ /// The IDs passed to each call to buildView, in call order.
+ List createdIds = [];
+
+ /// A map of creation IDs to fake map instances.
+ Map mapInstances =
+ {};
+
+ PlatformMapStateRecorder get lastCreatedMap => mapInstances[createdIds.last]!;
+
+ /// Whether to add a small delay to async calls to simulate more realistic
+ /// async behavior (simulating the platform channel calls most
+ /// implementations will do).
+ ///
+ /// When true, requires tests to `pumpAndSettle` at the end of the test
+ /// to avoid exceptions.
+ bool simulatePlatformDelay = false;
+
+ /// Whether `dispose` has been called.
+ bool disposed = false;
+
+ /// Stream controller to inject events for testing.
+ final StreamController> mapEventStreamController =
+ StreamController>.broadcast();
+
+ @override
+ Future init(int mapId) async {}
+
+ @override
+ Future updateMapConfiguration(
+ MapConfiguration update, {
+ required int mapId,
+ }) async {
+ mapInstances[mapId]?.mapConfiguration = update;
+ await _fakeDelay();
+ }
+
+ @override
+ Future updateMarkers(
+ MarkerUpdates markerUpdates, {
+ required int mapId,
+ }) async {
+ mapInstances[mapId]?.markerUpdates.add(markerUpdates);
+ await _fakeDelay();
+ }
+
+ @override
+ Future updatePolygons(
+ PolygonUpdates polygonUpdates, {
+ required int mapId,
+ }) async {
+ mapInstances[mapId]?.polygonUpdates.add(polygonUpdates);
+ await _fakeDelay();
+ }
+
+ @override
+ Future updatePolylines(
+ PolylineUpdates polylineUpdates, {
+ required int mapId,
+ }) async {
+ mapInstances[mapId]?.polylineUpdates.add(polylineUpdates);
+ await _fakeDelay();
+ }
+
+ @override
+ Future updateCircles(
+ CircleUpdates circleUpdates, {
+ required int mapId,
+ }) async {
+ mapInstances[mapId]?.circleUpdates.add(circleUpdates);
+ await _fakeDelay();
+ }
+
+ @override
+ Future updateTileOverlays({
+ required Set newTileOverlays,
+ required int mapId,
+ }) async {
+ mapInstances[mapId]?.tileOverlaySets.add(newTileOverlays);
+ await _fakeDelay();
+ }
+
+ @override
+ Future clearTileCache(
+ TileOverlayId tileOverlayId, {
+ required int mapId,
+ }) async {}
+
+ @override
+ Future animateCamera(
+ CameraUpdate cameraUpdate, {
+ required int mapId,
+ }) async {}
+
+ @override
+ Future moveCamera(
+ CameraUpdate cameraUpdate, {
+ required int mapId,
+ }) async {}
+
+ @override
+ Future setMapStyle(
+ String? mapStyle, {
+ required int mapId,
+ }) async {}
+
+ @override
+ Future getVisibleRegion({
+ required int mapId,
+ }) async {
+ return LatLngBounds(
+ southwest: const LatLng(0, 0), northeast: const LatLng(0, 0));
+ }
+
+ @override
+ Future getScreenCoordinate(
+ LatLng latLng, {
+ required int mapId,
+ }) async {
+ return const ScreenCoordinate(x: 0, y: 0);
+ }
+
+ @override
+ Future getLatLng(
+ ScreenCoordinate screenCoordinate, {
+ required int mapId,
+ }) async {
+ return const LatLng(0, 0);
+ }
+
+ @override
+ Future showMarkerInfoWindow(
+ MarkerId markerId, {
+ required int mapId,
+ }) async {}
+
+ @override
+ Future hideMarkerInfoWindow(
+ MarkerId markerId, {
+ required int mapId,
+ }) async {}
+
+ @override
+ Future isMarkerInfoWindowShown(
+ MarkerId markerId, {
+ required int mapId,
+ }) async {
+ return false;
+ }
+
+ @override
+ Future getZoomLevel({
+ required int mapId,
+ }) async {
+ return 0.0;
+ }
+
+ @override
+ Future takeSnapshot({
+ required int mapId,
+ }) async {
+ return null;
+ }
+
+ @override
+ Stream onCameraMoveStarted({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onCameraMove({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onCameraIdle({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onMarkerTap({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onInfoWindowTap({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onMarkerDragStart({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onMarkerDrag({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onMarkerDragEnd({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onPolylineTap({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onPolygonTap({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onCircleTap({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onTap({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ Stream onLongPress({required int mapId}) {
+ return mapEventStreamController.stream.whereType();
+ }
+
+ @override
+ void dispose({required int mapId}) {
+ disposed = true;
+ }
+
+ @override
+ Widget buildViewWithConfiguration(
+ int creationId,
+ PlatformViewCreatedCallback onPlatformViewCreated, {
+ required MapWidgetConfiguration widgetConfiguration,
+ MapObjects mapObjects = const MapObjects(),
+ MapConfiguration mapConfiguration = const MapConfiguration(),
+ }) {
+ final PlatformMapStateRecorder? instance = mapInstances[creationId];
+ if (instance == null) {
+ createdIds.add(creationId);
+ mapInstances[creationId] = PlatformMapStateRecorder(
+ widgetConfiguration: widgetConfiguration,
+ mapConfiguration: mapConfiguration,
+ mapObjects: mapObjects);
+ onPlatformViewCreated(creationId);
+ }
+ return Container();
+ }
+
+ Future _fakeDelay() async {
+ if (!simulatePlatformDelay) {
+ return;
+ }
+ return Future.delayed(const Duration(microseconds: 1));
+ }
+}
+
+/// A fake implementation of a native map, which stores all the updates it is
+/// sent for inspection in tests.
+class PlatformMapStateRecorder {
+ PlatformMapStateRecorder({
+ required this.widgetConfiguration,
+ this.mapObjects = const MapObjects(),
+ this.mapConfiguration = const MapConfiguration(),
+ }) {
+ markerUpdates.add(MarkerUpdates.from(const {}, mapObjects.markers));
+ polygonUpdates
+ .add(PolygonUpdates.from(const {}, mapObjects.polygons));
+ polylineUpdates
+ .add(PolylineUpdates.from(const {}, mapObjects.polylines));
+ circleUpdates.add(CircleUpdates.from(const {}, mapObjects.circles));
+ tileOverlaySets.add(mapObjects.tileOverlays);
+ }
+
+ MapWidgetConfiguration widgetConfiguration;
+ MapObjects mapObjects;
+ MapConfiguration mapConfiguration;
+
+ final List markerUpdates = [];
+ final List polygonUpdates = [];
+ final List polylineUpdates = [];
+ final List circleUpdates = [];
+ final List> tileOverlaySets = >[];
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart
deleted file mode 100644
index c28ff1f4f55f..000000000000
--- a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart
+++ /dev/null
@@ -1,485 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:typed_data';
-
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:google_maps_flutter/google_maps_flutter.dart';
-
-class FakePlatformGoogleMap {
- FakePlatformGoogleMap(int id, Map params)
- : cameraPosition =
- CameraPosition.fromMap(params['initialCameraPosition']),
- channel = MethodChannel('plugins.flutter.io/google_maps_$id') {
- _ambiguate(TestDefaultBinaryMessengerBinding.instance)!
- .defaultBinaryMessenger
- .setMockMethodCallHandler(channel, onMethodCall);
- updateOptions(params['options'] as Map);
- updateMarkers(params);
- updatePolygons(params);
- updatePolylines(params);
- updateCircles(params);
- updateTileOverlays(Map.castFrom(params));
- }
-
- MethodChannel channel;
-
- CameraPosition? cameraPosition;
-
- bool? compassEnabled;
-
- bool? mapToolbarEnabled;
-
- CameraTargetBounds? cameraTargetBounds;
-
- MapType? mapType;
-
- MinMaxZoomPreference? minMaxZoomPreference;
-
- bool? rotateGesturesEnabled;
-
- bool? scrollGesturesEnabled;
-
- bool? tiltGesturesEnabled;
-
- bool? zoomGesturesEnabled;
-
- bool? zoomControlsEnabled;
-
- bool? liteModeEnabled;
-
- bool? trackCameraPosition;
-
- bool? myLocationEnabled;
-
- bool? trafficEnabled;
-
- bool? buildingsEnabled;
-
- bool? myLocationButtonEnabled;
-
- List? padding;
-
- Set markerIdsToRemove = {};
-
- Set markersToAdd = {};
-
- Set markersToChange = {};
-
- Set polygonIdsToRemove = {};
-
- Set polygonsToAdd = {};
-
- Set polygonsToChange = {};
-
- Set polylineIdsToRemove = {};
-
- Set polylinesToAdd = {};
-
- Set polylinesToChange = {};
-
- Set circleIdsToRemove = {};
-
- Set circlesToAdd = {};
-
- Set circlesToChange = {};
-
- Set tileOverlayIdsToRemove = {};
-
- Set tileOverlaysToAdd = {};
-
- Set tileOverlaysToChange = {};
-
- Future onMethodCall(MethodCall call) {
- switch (call.method) {
- case 'map#update':
- final Map arguments =
- (call.arguments as Map