diff --git a/.agents/skills/stage-site/SKILL.md b/.agents/skills/stage-site/SKILL.md new file mode 100644 index 0000000000..6e61bad0df --- /dev/null +++ b/.agents/skills/stage-site/SKILL.md @@ -0,0 +1,54 @@ +--- +name: stage-site +description: >- + Stages the Flutter site locally. This includes checking for broken links, + verifying excerpts, updating formatting, and serving the dev site. +--- + +# Stage the Flutter site + +Before committing changes or reviewing a PR locally, +it is important to stage the site and ensure everything works correctly. +Follow these steps to stage the site: + +## 1. Check for broken links + +Run the following command to check for any unlinked or broken +Markdown link references in the site output: + +```bash +dart run dash_site check-link-references +``` + +Alternatively, to verify all links between pages on the site work: + +```bash +dart run dash_site check-links +``` + +If any broken links are found, try to patch them or alert the user. + +## 2. Sync code excerpts + +Ensure that any changed code excerpts are properly run and synced to the Markdown files: + +```bash +dart run dash_site refresh-excerpts +``` + +## 3. Update formatting + +Update the formatting of the site examples and tools to ensure +they follow the official Dart format: + +```bash +dart run dash_site format-dart +``` + +## 4. Stage the site locally + +Finally, serve a local development server of the site so the user can preview the changes: + +```bash +dart run dash_site serve +``` diff --git a/.agents/skills/update-llms-text/SKILL.md b/.agents/skills/update-llms-text/SKILL.md new file mode 100644 index 0000000000..c3c225bbd2 --- /dev/null +++ b/.agents/skills/update-llms-text/SKILL.md @@ -0,0 +1,64 @@ +--- +name: update-llms-text +description: Update and maintain the llms.txt file. Use when the user wants to add sections, add links, correct information, or review the llms.txt file. +disable-model-invocation: true +argument-hint: "[action or description of changes]" +--- + +# Update llms.txt + +Update `src/content/llms.txt`, a curated resource list that +helps LLMs understand the Flutter framework. + +Always start by reading the existing `src/content/llms.txt` to +understand the current structure and content before making changes. + +## Actions + +Based on the user-provided action, perform one of the following tasks: + +### Add a section + +When asked to add a new section: + +1. If not provided, ask for the section title and links. +2. Place the new section in a logical location relative to existing sections. + +### Add links + +When asked to add new links to an existing section: + +1. If not provided, ask for the target section and links to add. +2. For each link provided: + + 1. Load and analyze its source file to identify its title and + determine an appropriate description. + 2. Append the link to the specified section, + matching the existing Markdown-based format: + + ```md + - [Title](URL): Description. + ``` + +### Correct information + +When asked to correct information: + +1. If not provided, Ask for the incorrect information and the correction. +2. Update the file with the corrected information. + +### Review the file + +When asked to review the file: + +1. Read the entire file. +2. Check for duplicate entries, broken links, inconsistent formatting, + spelling and grammar errors, and general opportunities for improvement. +3. Report findings and suggestions to the user, but + don't make changes without approval. + +## Formatting rules + +- Each link follows the format: `- [Title](URL): Description.` +- Sections are separated by a blank line. +- Keep descriptions concise and informative. diff --git a/.agents/skills/validate-pr/SKILL.md b/.agents/skills/validate-pr/SKILL.md new file mode 100644 index 0000000000..1e27a3362b --- /dev/null +++ b/.agents/skills/validate-pr/SKILL.md @@ -0,0 +1,49 @@ +--- +name: validate-pr +description: >- + Stages the Flutter site locally. This includes checking for broken links, + verifying excerpts, updating formatting, and serving the dev site. +--- + +# Stage the Flutter site + +Before committing changes or reviewing a PR locally, +it is important to stage the site and ensure everything works correctly. +Follow these steps to stage the site: + +## 1. Sync code excerpts + +Ensure that any changed code excerpts are properly run and synced to the +Markdown files: + +```bash +dart run dash_site refresh-excerpts +``` + +## 2. Update formatting + +Update the formatting of the site examples and tools to ensure +they follow the official Dart format: + +```bash +dart run dash_site format-dart +``` + +## 3. Check for broken links + +Run the following command to check for any unlinked or broken +Markdown link references in the site output: + +```bash +dart run dash_site check-link-references +``` + +If any broken links are found, try to patch them or alert the user. + +## 4. Stage the site locally + +Finally, serve a local development server of the site so the user can preview the changes: + +```bash +dart run dash_site serve +``` diff --git a/.gemini/config.yaml b/.gemini/config.yaml new file mode 100644 index 0000000000..cb433867fc --- /dev/null +++ b/.gemini/config.yaml @@ -0,0 +1,17 @@ +# Configuration for the Gemini Code Assist review bot on GitHub. +# Relevant documentation: https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github + +code_review: + # Only leave review comments for medium or higher severity issues. + comment_severity_threshold: MEDIUM + # We don't need to include drafts. + pull_request_opened: + # The documentation can be referenced if needed. + help: false + # The summaries are often too verbose to be helpful, and we expect + # contributors to provide sufficient detail in the PR description. + summary: false + include_drafts: false + +# We have lots of fun already and less noise is fun too. +have_fun: false diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..363583a4d8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,73 @@ +# AGENTS.md + +This file provides important project context and guidance to +agentic coding and writing tools when working with them on +content and code in this repository. + +## Project overview + +This repository contains the source code and content for +the Flutter framework's documentation website. +It is hosted at `docs.flutter.dev` and shouldn't be confused with +the marketing site at `flutter.dev` or the API docs at `api.flutter.dev`. + +The website is statically built and implemented with +Dart and the [Jaspr](https://jaspr.site) web framework. +For loading content, data, and some of its assets, +it uses [Jaspr Content](https://docs.jaspr.site/content). + +### Directory structure + +- `src/content/`: + Markdown-based documentation pages. +- `src/data/`: + YAML and JSON data files used by the site, such as + for the sidenav, glossary, and various indices. +- `src/_includes/`: + Liquid partial files written in Markdown. +- `examples/`: + A pub workspace containing Dart and Flutter code examples, + referenced by code excerpts in the Markdown files. +- `site/`: + The site implementation written in Dart using Jaspr and Jaspr Content. +- `tool/dash_site/`: + CLI tool for site development and maintenance. + +## Common commands + +While working on the site, +you might need to run these commands: + +```bash +# Install or update Dart dependencies: +dart pub get + +# Serve a dev server of the site locally: +dart run dash_site serve + +# Build a production version of the site: +dart run dash_site build + +# Sync code excerpts to Markdown files: +dart run dash_site refresh-excerpts + +# Learn what other commands are available: +dart run dash_site --help +``` + +## Content guidelines + +### Writing style + +Writing should be consistent across the site and follow the +[Google developer documentation style guide](https://developers.google.com/style/). + +#### Line breaks + +In Markdown files, use [semantic line breaks](https://sembr.org/) and +try to keep each line under 80 characters long. + +## Coding guidelines + +All Dart code should follow [Effective Dart](https://dart.dev/effective-dart), +unless purposefully not for explanatory or pedagogical purposes. diff --git a/README.md b/README.md index f0b0431cb2..798d632678 100644 --- a/README.md +++ b/README.md @@ -33,28 +33,28 @@ it's easiest to make changes using the GitHub UI. If you have an issue with the API docs on [api.flutter.dev](https://api.flutter.dev), -please file those issues on +please file them on the [`flutter/flutter`](https://github.com/flutter/flutter/issues) repo, not on this (`flutter/website`) repo. The API docs are embedded in Flutter's source code, -so the engineering team handles those. +so the engineering team handles them. ## Before you submit a PR We love it when the community gets involved in improving our docs! -But here are a few notes to keep in mind before you submit a PR: +Here are a few things to keep in mind before you submit a PR: - When triaging issues, we sometimes label an issue with the tag **PRs welcome**. - But we welcome PRs on other issues as well— + However, we welcome PRs on other issues as well— it doesn't have to be tagged with that label. -- Please don't run our docs through Grammarly (or similar) +- Please don't run our docs through Grammarly (or a similar tool) and submit those changes as a PR. -- We follow the [Google Developer Documentation Style Guidelines][] — - for example, don't use "i.e." or "e.g.", - avoid writing in first person, - and avoid writing in future tense. +- We follow the [Google Developer Documentation Style Guidelines][]— + for example, avoid "i.e." or "e.g.", + avoid writing in the first person, + and avoid the future tense. You can start with the [style guide highlights](https://developers.google.com/style/highlights) or the [word list](https://developers.google.com/style/word-list), @@ -76,7 +76,7 @@ Often you can make changes using the GitHub UI. If needed, we can stage the changes automatically in your pull request. If your change involves code samples, adds/removes pages, or affects navigation, -do consider building and testing your work before submitting. +please build and test your work before submitting. If you want or need to build the site, follow the steps below. @@ -94,8 +94,8 @@ install the latest stable release of Flutter, which includes Dart. If you don't have Flutter or need to update, follow the instructions at [Install Flutter][] or [Upgrading Flutter][]. -If you already have Flutter installed, verify it's on your path -and already the latest stable version: +If you already have Flutter installed, verify it's in your path +and is the latest stable version: ```console flutter --version @@ -122,7 +122,7 @@ git clone https://github.com/flutter/website.git ## Set up your local environment and serve changes Before you continue setting up the site infrastructure, -verify the correct versions of Flutter are set up and available by +verify that the correct version of Flutter is set up and available by following the instructions in [Get the prerequisites](#get-the-prerequisites). 1. _Optional:_ After cloning the repo, @@ -181,17 +181,17 @@ following the instructions in [Get the prerequisites](#get-the-prerequisites). If you've made changes to the code in the `/examples`, `/site`, or `/tool` directories, -commit your work, then run the following command to -verify it is up to date and matches the site standards. +commit your work, and then run the following command to +verify that it is up to date and matches site standards. ```terminal dart run dash_site check-all ``` If this script reports any errors or warnings, -then address those issues and rerun the command. -If you have any issues, leave a comment on your issue or pull request, -and we'll try our best to help you. +address them and rerun the command. +If you have questions, leave a comment on your issue or pull request, +and we'll do our best to help. You can also chat with us on the `#hackers-devrel` channel on the [Flutter contributors Discord][]! @@ -199,9 +199,9 @@ on the [Flutter contributors Discord][]! ### Check links -If you've made changes to the content and would like to check all -internal links are functional and Markdown link references are valid, -build the site locally and then run the following commands. +If you've made changes to the content and want to check that all +internal links and Markdown link references are valid, +build the site locally and run the following commands. 1. Build the site locally. @@ -215,7 +215,7 @@ build the site locally and then run the following commands. dart run dash_site check-link-references ``` -1. Validate all internal links are valid. +1. Verify that all internal links are valid. > [!NOTE] > This command relies on the `firebase` CLI tool being on your PATH. @@ -227,14 +227,14 @@ build the site locally and then run the following commands. ``` If either command reports any errors or warnings, -then address those issues, rebuild the site, and rerun the command. +address them, rebuild the site, and rerun the command. [firebase-install]: https://firebase.google.com/docs/cli#setup_update_cli ### Refresh code excerpts A build that fails with the error -`Error: Some code excerpts needed to be updated!` +`Error: Some code excerpts need to be updated!` means that one or more code excerpts in the site Markdown files aren't identical to the code regions declared in the corresponding `.dart` files. @@ -250,4 +250,4 @@ run `dart run dash_site refresh-excerpts`. To learn more about creating, editing, and using code excerpts, check out the [excerpt updater package documentation][]. -[excerpt updater package documentation]: https://github.com/dart-lang/site-shared/tree/main/pkgs/excerpter#readme +[excerpt updater package documentation]: https://github.com/flutter/website/tree/main/packages/excerpter#readme diff --git a/examples/_animation/basic_hero_animation/pubspec.yaml b/examples/_animation/basic_hero_animation/pubspec.yaml index cc4a0e1eda..452cb540e7 100644 --- a/examples/_animation/basic_hero_animation/pubspec.yaml +++ b/examples/_animation/basic_hero_animation/pubspec.yaml @@ -5,7 +5,7 @@ description: >- resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/_animation/basic_radial_hero_animation/pubspec.yaml b/examples/_animation/basic_radial_hero_animation/pubspec.yaml index 923af53b8d..e09286c618 100644 --- a/examples/_animation/basic_radial_hero_animation/pubspec.yaml +++ b/examples/_animation/basic_radial_hero_animation/pubspec.yaml @@ -6,7 +6,7 @@ description: >- resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/_animation/basic_staggered_animation/pubspec.yaml b/examples/_animation/basic_staggered_animation/pubspec.yaml index fec2ecd870..e5eadb49c2 100644 --- a/examples/_animation/basic_staggered_animation/pubspec.yaml +++ b/examples/_animation/basic_staggered_animation/pubspec.yaml @@ -4,7 +4,7 @@ description: An introductory example to staggered animations. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/_animation/hero_animation/pubspec.yaml b/examples/_animation/hero_animation/pubspec.yaml index aa986479b8..8e00c7d92b 100644 --- a/examples/_animation/hero_animation/pubspec.yaml +++ b/examples/_animation/hero_animation/pubspec.yaml @@ -4,7 +4,7 @@ description: Shows how to create a simple Hero transition. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/_animation/radial_hero_animation/pubspec.yaml b/examples/_animation/radial_hero_animation/pubspec.yaml index ddbb1618ba..f0a50efef7 100644 --- a/examples/_animation/radial_hero_animation/pubspec.yaml +++ b/examples/_animation/radial_hero_animation/pubspec.yaml @@ -6,7 +6,7 @@ description: >- resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/_animation/radial_hero_animation_animate_rectclip/pubspec.yaml b/examples/_animation/radial_hero_animation_animate_rectclip/pubspec.yaml index 511387bc64..140956d18b 100644 --- a/examples/_animation/radial_hero_animation_animate_rectclip/pubspec.yaml +++ b/examples/_animation/radial_hero_animation_animate_rectclip/pubspec.yaml @@ -6,7 +6,7 @@ description: >- resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/_animation/staggered_pic_selection/pubspec.yaml b/examples/_animation/staggered_pic_selection/pubspec.yaml index af0ad5c1c5..0b86b29dec 100644 --- a/examples/_animation/staggered_pic_selection/pubspec.yaml +++ b/examples/_animation/staggered_pic_selection/pubspec.yaml @@ -4,7 +4,7 @@ description: A more complex staggered animation example. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/accessibility/pubspec.yaml b/examples/accessibility/pubspec.yaml index 30d6d9a6df..08ac73ef9b 100644 --- a/examples/accessibility/pubspec.yaml +++ b/examples/accessibility/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/analysis_options.yaml b/examples/analysis_options.yaml index 66d698da6e..a53c0054de 100644 --- a/examples/analysis_options.yaml +++ b/examples/analysis_options.yaml @@ -37,7 +37,9 @@ linter: - prefer_final_fields - prefer_relative_imports - prefer_single_quotes + - simplify_variable_pattern - sort_unnamed_constructors_first + - specify_nonobvious_property_types - strict_top_level_inference - switch_on_type - test_types_in_equals diff --git a/examples/animation/animate0/pubspec.yaml b/examples/animation/animate0/pubspec.yaml index 5711059279..c7bd2694c8 100644 --- a/examples/animation/animate0/pubspec.yaml +++ b/examples/animation/animate0/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/animation/animate1/pubspec.yaml b/examples/animation/animate1/pubspec.yaml index 3ad85acc0d..026e6dde10 100644 --- a/examples/animation/animate1/pubspec.yaml +++ b/examples/animation/animate1/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/animation/animate2/pubspec.yaml b/examples/animation/animate2/pubspec.yaml index f13b7eb479..2c85add160 100644 --- a/examples/animation/animate2/pubspec.yaml +++ b/examples/animation/animate2/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/animation/animate3/pubspec.yaml b/examples/animation/animate3/pubspec.yaml index 46c72e44da..99639d6afb 100644 --- a/examples/animation/animate3/pubspec.yaml +++ b/examples/animation/animate3/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/animation/animate4/pubspec.yaml b/examples/animation/animate4/pubspec.yaml index bc10124af3..add514f1ff 100644 --- a/examples/animation/animate4/pubspec.yaml +++ b/examples/animation/animate4/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/animation/animate5/pubspec.yaml b/examples/animation/animate5/pubspec.yaml index 71cafc3bb6..b5aa9b8e78 100644 --- a/examples/animation/animate5/pubspec.yaml +++ b/examples/animation/animate5/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/app-architecture/command/pubspec.yaml b/examples/app-architecture/command/pubspec.yaml index e61093a2cb..9d3625d102 100644 --- a/examples/app-architecture/command/pubspec.yaml +++ b/examples/app-architecture/command/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for command cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/app-architecture/offline_first/pubspec.yaml b/examples/app-architecture/offline_first/pubspec.yaml index ad9c6e9568..1042384780 100644 --- a/examples/app-architecture/offline_first/pubspec.yaml +++ b/examples/app-architecture/offline_first/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for offline_first cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/app-architecture/optimistic_state/pubspec.yaml b/examples/app-architecture/optimistic_state/pubspec.yaml index 63d76d6b0d..0bbe011daf 100644 --- a/examples/app-architecture/optimistic_state/pubspec.yaml +++ b/examples/app-architecture/optimistic_state/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for optimistic_state cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/app-architecture/result/pubspec.yaml b/examples/app-architecture/result/pubspec.yaml index c4af6b828c..5357989125 100644 --- a/examples/app-architecture/result/pubspec.yaml +++ b/examples/app-architecture/result/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for result cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/app-architecture/todo_data_service/pubspec.yaml b/examples/app-architecture/todo_data_service/pubspec.yaml index 47f3b78959..d20f780132 100644 --- a/examples/app-architecture/todo_data_service/pubspec.yaml +++ b/examples/app-architecture/todo_data_service/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for key_value_data cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/animation/animated_container/pubspec.yaml b/examples/cookbook/animation/animated_container/pubspec.yaml index 73f8a999af..f43c4febd7 100644 --- a/examples/cookbook/animation/animated_container/pubspec.yaml +++ b/examples/cookbook/animation/animated_container/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for cookbook. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/animation/opacity_animation/pubspec.yaml b/examples/cookbook/animation/opacity_animation/pubspec.yaml index 4057999250..ae343964a5 100644 --- a/examples/cookbook/animation/opacity_animation/pubspec.yaml +++ b/examples/cookbook/animation/opacity_animation/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for cookbook. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/animation/page_route_animation/pubspec.yaml b/examples/cookbook/animation/page_route_animation/pubspec.yaml index 04ae456a31..647f1aefe8 100644 --- a/examples/cookbook/animation/page_route_animation/pubspec.yaml +++ b/examples/cookbook/animation/page_route_animation/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for cookbook. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/animation/physics_simulation/pubspec.yaml b/examples/cookbook/animation/physics_simulation/pubspec.yaml index 5042d933bb..3355fc0989 100644 --- a/examples/cookbook/animation/physics_simulation/pubspec.yaml +++ b/examples/cookbook/animation/physics_simulation/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for cookbook. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/design/drawer/pubspec.yaml b/examples/cookbook/design/drawer/pubspec.yaml index e6596d3828..11b935bdb4 100644 --- a/examples/cookbook/design/drawer/pubspec.yaml +++ b/examples/cookbook/design/drawer/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for drawer cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/design/fonts/pubspec.yaml b/examples/cookbook/design/fonts/pubspec.yaml index c6073d5b59..d329b5580c 100644 --- a/examples/cookbook/design/fonts/pubspec.yaml +++ b/examples/cookbook/design/fonts/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/design/orientation/pubspec.yaml b/examples/cookbook/design/orientation/pubspec.yaml index cb8203767a..b7c0810548 100644 --- a/examples/cookbook/design/orientation/pubspec.yaml +++ b/examples/cookbook/design/orientation/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for orientation cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/design/package_fonts/pubspec.yaml b/examples/cookbook/design/package_fonts/pubspec.yaml index 1b1f4a94a3..750cb218bd 100644 --- a/examples/cookbook/design/package_fonts/pubspec.yaml +++ b/examples/cookbook/design/package_fonts/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/design/snackbars/pubspec.yaml b/examples/cookbook/design/snackbars/pubspec.yaml index 9d21917247..049b7568ca 100644 --- a/examples/cookbook/design/snackbars/pubspec.yaml +++ b/examples/cookbook/design/snackbars/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for snackbars cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/design/tabs/pubspec.yaml b/examples/cookbook/design/tabs/pubspec.yaml index 63bcb11813..dd805ef130 100644 --- a/examples/cookbook/design/tabs/pubspec.yaml +++ b/examples/cookbook/design/tabs/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for tabs cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/design/themes/pubspec.yaml b/examples/cookbook/design/themes/pubspec.yaml index 86c19f8081..1aae8bf2e8 100644 --- a/examples/cookbook/design/themes/pubspec.yaml +++ b/examples/cookbook/design/themes/pubspec.yaml @@ -3,12 +3,12 @@ description: Sample code for themes cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: sdk: flutter - google_fonts: ^7.0.0 + google_fonts: ^8.0.0 flutter: uses-material-design: true diff --git a/examples/cookbook/effects/download_button/pubspec.yaml b/examples/cookbook/effects/download_button/pubspec.yaml index c583790bce..5d1a3aff6f 100644 --- a/examples/cookbook/effects/download_button/pubspec.yaml +++ b/examples/cookbook/effects/download_button/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/effects/drag_a_widget/pubspec.yaml b/examples/cookbook/effects/drag_a_widget/pubspec.yaml index 14dd510f0b..401a966e75 100644 --- a/examples/cookbook/effects/drag_a_widget/pubspec.yaml +++ b/examples/cookbook/effects/drag_a_widget/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/effects/expandable_fab/pubspec.yaml b/examples/cookbook/effects/expandable_fab/pubspec.yaml index 39ab671d30..7963d1092f 100644 --- a/examples/cookbook/effects/expandable_fab/pubspec.yaml +++ b/examples/cookbook/effects/expandable_fab/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/effects/gradient_bubbles/pubspec.yaml b/examples/cookbook/effects/gradient_bubbles/pubspec.yaml index f0f38e21bd..efca27919e 100644 --- a/examples/cookbook/effects/gradient_bubbles/pubspec.yaml +++ b/examples/cookbook/effects/gradient_bubbles/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/effects/nested_nav/pubspec.yaml b/examples/cookbook/effects/nested_nav/pubspec.yaml index f89f0096fa..fe6c2cd61d 100644 --- a/examples/cookbook/effects/nested_nav/pubspec.yaml +++ b/examples/cookbook/effects/nested_nav/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/effects/parallax_scrolling/pubspec.yaml b/examples/cookbook/effects/parallax_scrolling/pubspec.yaml index b9cbafb93d..3d8b960b57 100644 --- a/examples/cookbook/effects/parallax_scrolling/pubspec.yaml +++ b/examples/cookbook/effects/parallax_scrolling/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/effects/shimmer_loading/pubspec.yaml b/examples/cookbook/effects/shimmer_loading/pubspec.yaml index e16b5b4ce6..601103143c 100644 --- a/examples/cookbook/effects/shimmer_loading/pubspec.yaml +++ b/examples/cookbook/effects/shimmer_loading/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/effects/staggered_menu_animation/lib/animation_delays.dart b/examples/cookbook/effects/staggered_menu_animation/lib/animation_delays.dart index 74c73595d6..4ffda0e059 100644 --- a/examples/cookbook/effects/staggered_menu_animation/lib/animation_delays.dart +++ b/examples/cookbook/effects/staggered_menu_animation/lib/animation_delays.dart @@ -16,7 +16,7 @@ class _MenuState extends State with SingleTickerProviderStateMixin { static const _staggerTime = Duration(milliseconds: 50); static const _buttonDelayTime = Duration(milliseconds: 150); static const _buttonTime = Duration(milliseconds: 500); - final _animationDuration = + final Duration _animationDuration = _initialDelayTime + (_staggerTime * _menuTitles.length) + _buttonDelayTime + diff --git a/examples/cookbook/effects/staggered_menu_animation/lib/main.dart b/examples/cookbook/effects/staggered_menu_animation/lib/main.dart index 94da791eb0..41fd51ef11 100644 --- a/examples/cookbook/effects/staggered_menu_animation/lib/main.dart +++ b/examples/cookbook/effects/staggered_menu_animation/lib/main.dart @@ -127,7 +127,7 @@ class _MenuState extends State with SingleTickerProviderStateMixin { static const _staggerTime = Duration(milliseconds: 50); static const _buttonDelayTime = Duration(milliseconds: 150); static const _buttonTime = Duration(milliseconds: 500); - final _animationDuration = + final Duration _animationDuration = _initialDelayTime + (_staggerTime * _menuTitles.length) + _buttonDelayTime + diff --git a/examples/cookbook/effects/staggered_menu_animation/lib/step3.dart b/examples/cookbook/effects/staggered_menu_animation/lib/step3.dart index cd0016bff7..310d2993d2 100644 --- a/examples/cookbook/effects/staggered_menu_animation/lib/step3.dart +++ b/examples/cookbook/effects/staggered_menu_animation/lib/step3.dart @@ -59,7 +59,7 @@ class _MenuState extends State with SingleTickerProviderStateMixin { static const _staggerTime = Duration(milliseconds: 50); static const _buttonDelayTime = Duration(milliseconds: 150); static const _buttonTime = Duration(milliseconds: 500); - final _animationDuration = + final Duration _animationDuration = _initialDelayTime + (_staggerTime * _menuTitles.length) + _buttonDelayTime + diff --git a/examples/cookbook/effects/staggered_menu_animation/lib/step4.dart b/examples/cookbook/effects/staggered_menu_animation/lib/step4.dart index 18c93bbe6c..4a78dcc454 100644 --- a/examples/cookbook/effects/staggered_menu_animation/lib/step4.dart +++ b/examples/cookbook/effects/staggered_menu_animation/lib/step4.dart @@ -69,7 +69,7 @@ class _MenuState extends State with SingleTickerProviderStateMixin { static const _staggerTime = Duration(milliseconds: 50); static const _buttonDelayTime = Duration(milliseconds: 150); static const _buttonTime = Duration(milliseconds: 500); - final _animationDuration = + final Duration _animationDuration = _initialDelayTime + (_staggerTime * _menuTitles.length) + _buttonDelayTime + diff --git a/examples/cookbook/effects/staggered_menu_animation/pubspec.yaml b/examples/cookbook/effects/staggered_menu_animation/pubspec.yaml index b8dc8e2230..5efea85222 100644 --- a/examples/cookbook/effects/staggered_menu_animation/pubspec.yaml +++ b/examples/cookbook/effects/staggered_menu_animation/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/effects/typing_indicator/pubspec.yaml b/examples/cookbook/effects/typing_indicator/pubspec.yaml index fd5e513e96..62b73a888f 100644 --- a/examples/cookbook/effects/typing_indicator/pubspec.yaml +++ b/examples/cookbook/effects/typing_indicator/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/forms/focus/pubspec.yaml b/examples/cookbook/forms/focus/pubspec.yaml index c8d3c62283..1ad5e388e0 100644 --- a/examples/cookbook/forms/focus/pubspec.yaml +++ b/examples/cookbook/forms/focus/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for focus cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/forms/retrieve_input/pubspec.yaml b/examples/cookbook/forms/retrieve_input/pubspec.yaml index fece8e6519..baf3ca5866 100644 --- a/examples/cookbook/forms/retrieve_input/pubspec.yaml +++ b/examples/cookbook/forms/retrieve_input/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for retrieve_input cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/forms/text_field_changes/pubspec.yaml b/examples/cookbook/forms/text_field_changes/pubspec.yaml index 51ec9a6059..efb7cc39fd 100644 --- a/examples/cookbook/forms/text_field_changes/pubspec.yaml +++ b/examples/cookbook/forms/text_field_changes/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for text_field_changes resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/forms/text_input/pubspec.yaml b/examples/cookbook/forms/text_input/pubspec.yaml index 8bb9be098b..ef20a53503 100644 --- a/examples/cookbook/forms/text_input/pubspec.yaml +++ b/examples/cookbook/forms/text_input/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/forms/validation/pubspec.yaml b/examples/cookbook/forms/validation/pubspec.yaml index 375f2f3cdf..70b41e7748 100644 --- a/examples/cookbook/forms/validation/pubspec.yaml +++ b/examples/cookbook/forms/validation/pubspec.yaml @@ -3,7 +3,7 @@ description: Use Form widget to perform form validation. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/games/achievements_leaderboards/pubspec.yaml b/examples/cookbook/games/achievements_leaderboards/pubspec.yaml index 7dcdffe97b..bd37990e9b 100644 --- a/examples/cookbook/games/achievements_leaderboards/pubspec.yaml +++ b/examples/cookbook/games/achievements_leaderboards/pubspec.yaml @@ -3,7 +3,7 @@ description: Games services resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/games/firestore_multiplayer/lib/multiplayer/firestore_controller.dart b/examples/cookbook/games/firestore_multiplayer/lib/multiplayer/firestore_controller.dart index 67548fe03f..d7b2c3cba1 100644 --- a/examples/cookbook/games/firestore_multiplayer/lib/multiplayer/firestore_controller.dart +++ b/examples/cookbook/games/firestore_multiplayer/lib/multiplayer/firestore_controller.dart @@ -17,9 +17,11 @@ class FirestoreController { /// For now, there is only one match. But in order to be ready /// for match-making, put it in a Firestore collection called matches. - late final _matchRef = instance.collection('matches').doc('match_1'); + late final DocumentReference> _matchRef = instance + .collection('matches') + .doc('match_1'); - late final _areaOneRef = _matchRef + late final DocumentReference> _areaOneRef = _matchRef .collection('areas') .doc('area_one') .withConverter>( @@ -27,7 +29,7 @@ class FirestoreController { toFirestore: _cardsToFirestore, ); - late final _areaTwoRef = _matchRef + late final DocumentReference> _areaTwoRef = _matchRef .collection('areas') .doc('area_two') .withConverter>( diff --git a/examples/cookbook/games/firestore_multiplayer/pubspec.yaml b/examples/cookbook/games/firestore_multiplayer/pubspec.yaml index 7a97873581..0ab6675f30 100644 --- a/examples/cookbook/games/firestore_multiplayer/pubspec.yaml +++ b/examples/cookbook/games/firestore_multiplayer/pubspec.yaml @@ -3,7 +3,7 @@ description: Firestore multiplayer resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/gestures/dismissible/pubspec.yaml b/examples/cookbook/gestures/dismissible/pubspec.yaml index aa207baf19..378e6d9658 100644 --- a/examples/cookbook/gestures/dismissible/pubspec.yaml +++ b/examples/cookbook/gestures/dismissible/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for dismissible cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/gestures/handling_taps/pubspec.yaml b/examples/cookbook/gestures/handling_taps/pubspec.yaml index 2675e9dc2a..95eaa9232f 100644 --- a/examples/cookbook/gestures/handling_taps/pubspec.yaml +++ b/examples/cookbook/gestures/handling_taps/pubspec.yaml @@ -3,7 +3,7 @@ description: Example on handling_taps cookbook recipe. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/gestures/ripples/pubspec.yaml b/examples/cookbook/gestures/ripples/pubspec.yaml index 0c126e36db..a82c2f06ed 100644 --- a/examples/cookbook/gestures/ripples/pubspec.yaml +++ b/examples/cookbook/gestures/ripples/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for ripples cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/images/cached_images/pubspec.yaml b/examples/cookbook/images/cached_images/pubspec.yaml index 687da081bc..a092e575bc 100644 --- a/examples/cookbook/images/cached_images/pubspec.yaml +++ b/examples/cookbook/images/cached_images/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/images/fading_in_images/pubspec.yaml b/examples/cookbook/images/fading_in_images/pubspec.yaml index d179d60577..3011f28a21 100644 --- a/examples/cookbook/images/fading_in_images/pubspec.yaml +++ b/examples/cookbook/images/fading_in_images/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/images/network_image/pubspec.yaml b/examples/cookbook/images/network_image/pubspec.yaml index 1d604e225a..f3a207ebc9 100644 --- a/examples/cookbook/images/network_image/pubspec.yaml +++ b/examples/cookbook/images/network_image/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/lists/basic_list/pubspec.yaml b/examples/cookbook/lists/basic_list/pubspec.yaml index 28d00d2a64..0620ad3b5b 100644 --- a/examples/cookbook/lists/basic_list/pubspec.yaml +++ b/examples/cookbook/lists/basic_list/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/lists/floating_app_bar/pubspec.yaml b/examples/cookbook/lists/floating_app_bar/pubspec.yaml index d2953b8998..d297055c43 100644 --- a/examples/cookbook/lists/floating_app_bar/pubspec.yaml +++ b/examples/cookbook/lists/floating_app_bar/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for floating_app_bar cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/lists/grid_lists/pubspec.yaml b/examples/cookbook/lists/grid_lists/pubspec.yaml index 2c0b6bfdc0..35a02544f3 100644 --- a/examples/cookbook/lists/grid_lists/pubspec.yaml +++ b/examples/cookbook/lists/grid_lists/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/lists/horizontal_list/pubspec.yaml b/examples/cookbook/lists/horizontal_list/pubspec.yaml index a85bcd6f79..be94a25b14 100644 --- a/examples/cookbook/lists/horizontal_list/pubspec.yaml +++ b/examples/cookbook/lists/horizontal_list/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/lists/long_lists/pubspec.yaml b/examples/cookbook/lists/long_lists/pubspec.yaml index 8639ad326a..63957af6bb 100644 --- a/examples/cookbook/lists/long_lists/pubspec.yaml +++ b/examples/cookbook/lists/long_lists/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for long_lists cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/lists/mixed_list/pubspec.yaml b/examples/cookbook/lists/mixed_list/pubspec.yaml index c2723772a9..d7b17f2772 100644 --- a/examples/cookbook/lists/mixed_list/pubspec.yaml +++ b/examples/cookbook/lists/mixed_list/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for mixed_lists cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/lists/spaced_items/pubspec.yaml b/examples/cookbook/lists/spaced_items/pubspec.yaml index 16ee0eb2c9..fc02c27a95 100644 --- a/examples/cookbook/lists/spaced_items/pubspec.yaml +++ b/examples/cookbook/lists/spaced_items/pubspec.yaml @@ -3,7 +3,7 @@ description: Example for spaced_items cookbook recipe resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/maintenance/error_reporting/pubspec.yaml b/examples/cookbook/maintenance/error_reporting/pubspec.yaml index 78eedbffad..5a84312410 100644 --- a/examples/cookbook/maintenance/error_reporting/pubspec.yaml +++ b/examples/cookbook/maintenance/error_reporting/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/navigation/hero_animations/pubspec.yaml b/examples/cookbook/navigation/hero_animations/pubspec.yaml index c5e2811f65..8871b0abf0 100644 --- a/examples/cookbook/navigation/hero_animations/pubspec.yaml +++ b/examples/cookbook/navigation/hero_animations/pubspec.yaml @@ -3,7 +3,7 @@ description: Hero animations resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/navigation/named_routes/pubspec.yaml b/examples/cookbook/navigation/named_routes/pubspec.yaml index c0b224d314..37f509b98a 100644 --- a/examples/cookbook/navigation/named_routes/pubspec.yaml +++ b/examples/cookbook/navigation/named_routes/pubspec.yaml @@ -3,7 +3,7 @@ description: Named route example snippets. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/navigation/navigate_with_arguments/pubspec.yaml b/examples/cookbook/navigation/navigate_with_arguments/pubspec.yaml index 471490c067..d4cf141d03 100644 --- a/examples/cookbook/navigation/navigate_with_arguments/pubspec.yaml +++ b/examples/cookbook/navigation/navigate_with_arguments/pubspec.yaml @@ -3,7 +3,7 @@ description: Use Form widget to perform form validation. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/navigation/navigation_basics/pubspec.yaml b/examples/cookbook/navigation/navigation_basics/pubspec.yaml index ab8f297e6a..ba4cd9fef0 100644 --- a/examples/cookbook/navigation/navigation_basics/pubspec.yaml +++ b/examples/cookbook/navigation/navigation_basics/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/navigation/passing_data/pubspec.yaml b/examples/cookbook/navigation/passing_data/pubspec.yaml index 48d7094736..1018d69883 100644 --- a/examples/cookbook/navigation/passing_data/pubspec.yaml +++ b/examples/cookbook/navigation/passing_data/pubspec.yaml @@ -3,7 +3,7 @@ description: Passing data resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/navigation/returning_data/pubspec.yaml b/examples/cookbook/navigation/returning_data/pubspec.yaml index d3a47ddb71..2801c4ef58 100644 --- a/examples/cookbook/navigation/returning_data/pubspec.yaml +++ b/examples/cookbook/navigation/returning_data/pubspec.yaml @@ -3,7 +3,7 @@ description: Returning data resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/networking/authenticated_requests/pubspec.yaml b/examples/cookbook/networking/authenticated_requests/pubspec.yaml index a0af40fb32..374a94f401 100644 --- a/examples/cookbook/networking/authenticated_requests/pubspec.yaml +++ b/examples/cookbook/networking/authenticated_requests/pubspec.yaml @@ -3,7 +3,7 @@ description: Authenticated HTTP request example snippets. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/networking/background_parsing/pubspec.yaml b/examples/cookbook/networking/background_parsing/pubspec.yaml index b9e251750e..eea62c3f94 100644 --- a/examples/cookbook/networking/background_parsing/pubspec.yaml +++ b/examples/cookbook/networking/background_parsing/pubspec.yaml @@ -3,7 +3,7 @@ description: Background parsing resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/networking/delete_data/pubspec.yaml b/examples/cookbook/networking/delete_data/pubspec.yaml index 13dd3c2a5d..44776f17d3 100644 --- a/examples/cookbook/networking/delete_data/pubspec.yaml +++ b/examples/cookbook/networking/delete_data/pubspec.yaml @@ -3,7 +3,7 @@ description: Delete Data resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/networking/fetch_data/lib/main.dart b/examples/cookbook/networking/fetch_data/lib/main.dart index 203c50faf1..7359e87703 100644 --- a/examples/cookbook/networking/fetch_data/lib/main.dart +++ b/examples/cookbook/networking/fetch_data/lib/main.dart @@ -10,6 +10,7 @@ import 'package:http/http.dart' as http; Future fetchAlbum() async { final response = await http.get( Uri.parse('https://jsonplaceholder.typicode.com/albums/1'), + headers: {'Accept': 'application/json'}, ); if (response.statusCode == 200) { diff --git a/examples/cookbook/networking/fetch_data/pubspec.yaml b/examples/cookbook/networking/fetch_data/pubspec.yaml index 40f8908d97..25493bff61 100644 --- a/examples/cookbook/networking/fetch_data/pubspec.yaml +++ b/examples/cookbook/networking/fetch_data/pubspec.yaml @@ -3,7 +3,7 @@ description: Fetch Data resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/networking/send_data/pubspec.yaml b/examples/cookbook/networking/send_data/pubspec.yaml index 1323b20e36..4272b5efe1 100644 --- a/examples/cookbook/networking/send_data/pubspec.yaml +++ b/examples/cookbook/networking/send_data/pubspec.yaml @@ -3,7 +3,7 @@ description: Send data resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/networking/update_data/pubspec.yaml b/examples/cookbook/networking/update_data/pubspec.yaml index 88e4048e46..fc33f66c24 100644 --- a/examples/cookbook/networking/update_data/pubspec.yaml +++ b/examples/cookbook/networking/update_data/pubspec.yaml @@ -3,7 +3,7 @@ description: Update Data resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/networking/web_sockets/lib/main.dart b/examples/cookbook/networking/web_sockets/lib/main.dart index 3d4bebb7dc..15767bd1a8 100644 --- a/examples/cookbook/networking/web_sockets/lib/main.dart +++ b/examples/cookbook/networking/web_sockets/lib/main.dart @@ -28,8 +28,8 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { final TextEditingController _controller = TextEditingController(); // #docregion connect - final _channel = WebSocketChannel.connect( - Uri.parse('wss://echo.websocket.events'), + final WebSocketChannel _channel = WebSocketChannel.connect( + Uri.parse('wss://echo.websocket.org'), ); // #enddocregion connect diff --git a/examples/cookbook/networking/web_sockets/pubspec.yaml b/examples/cookbook/networking/web_sockets/pubspec.yaml index e47bd01c01..560d1ff952 100644 --- a/examples/cookbook/networking/web_sockets/pubspec.yaml +++ b/examples/cookbook/networking/web_sockets/pubspec.yaml @@ -3,7 +3,7 @@ description: Web Sockets resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/persistence/key_value/pubspec.yaml b/examples/cookbook/persistence/key_value/pubspec.yaml index f74fc7c43f..0af68667a0 100644 --- a/examples/cookbook/persistence/key_value/pubspec.yaml +++ b/examples/cookbook/persistence/key_value/pubspec.yaml @@ -6,7 +6,7 @@ description: >- resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/persistence/reading_writing_files/pubspec.yaml b/examples/cookbook/persistence/reading_writing_files/pubspec.yaml index e5fed432f7..27d994ee97 100644 --- a/examples/cookbook/persistence/reading_writing_files/pubspec.yaml +++ b/examples/cookbook/persistence/reading_writing_files/pubspec.yaml @@ -3,7 +3,7 @@ description: Reading and Writing Files resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/persistence/sqlite/pubspec.yaml b/examples/cookbook/persistence/sqlite/pubspec.yaml index d8fd1de2aa..5f450f1135 100644 --- a/examples/cookbook/persistence/sqlite/pubspec.yaml +++ b/examples/cookbook/persistence/sqlite/pubspec.yaml @@ -3,7 +3,7 @@ description: Example of using sqflite plugin to access SQLite database. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/plugins/google_mobile_ads/pubspec.yaml b/examples/cookbook/plugins/google_mobile_ads/pubspec.yaml index 48c66f14f5..9ba2c786bc 100644 --- a/examples/cookbook/plugins/google_mobile_ads/pubspec.yaml +++ b/examples/cookbook/plugins/google_mobile_ads/pubspec.yaml @@ -3,7 +3,7 @@ description: Google mobile ads resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/plugins/picture_using_camera/pubspec.yaml b/examples/cookbook/plugins/picture_using_camera/pubspec.yaml index 1bdac46c47..7f217a355e 100644 --- a/examples/cookbook/plugins/picture_using_camera/pubspec.yaml +++ b/examples/cookbook/plugins/picture_using_camera/pubspec.yaml @@ -3,12 +3,12 @@ description: A new Flutter project. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: sdk: flutter - camera: ^0.11.2 + camera: ^0.12.0 path_provider: ^2.1.5 path: ^1.9.1 diff --git a/examples/cookbook/plugins/play_video/pubspec.yaml b/examples/cookbook/plugins/play_video/pubspec.yaml index 102c6204ed..525487a2d5 100644 --- a/examples/cookbook/plugins/play_video/pubspec.yaml +++ b/examples/cookbook/plugins/play_video/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/integration/introduction/pubspec.yaml b/examples/cookbook/testing/integration/introduction/pubspec.yaml index 8ab86b0651..6c2ef6efbb 100644 --- a/examples/cookbook/testing/integration/introduction/pubspec.yaml +++ b/examples/cookbook/testing/integration/introduction/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/integration/profiling/pubspec.yaml b/examples/cookbook/testing/integration/profiling/pubspec.yaml index 3e842a02d5..3117c9620c 100644 --- a/examples/cookbook/testing/integration/profiling/pubspec.yaml +++ b/examples/cookbook/testing/integration/profiling/pubspec.yaml @@ -3,7 +3,7 @@ description: Integration test profiling examples. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/unit/counter_app/pubspec.yaml b/examples/cookbook/testing/unit/counter_app/pubspec.yaml index da9cfd55b1..00cfb02445 100644 --- a/examples/cookbook/testing/unit/counter_app/pubspec.yaml +++ b/examples/cookbook/testing/unit/counter_app/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/unit/mocking/pubspec.yaml b/examples/cookbook/testing/unit/mocking/pubspec.yaml index aa1ac706c8..1df7c0a0ba 100644 --- a/examples/cookbook/testing/unit/mocking/pubspec.yaml +++ b/examples/cookbook/testing/unit/mocking/pubspec.yaml @@ -3,7 +3,7 @@ description: Use the Mockito package to mimic the behavior of services for testi resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/widget/finders/pubspec.yaml b/examples/cookbook/testing/widget/finders/pubspec.yaml index 3816cd3941..463e61ce66 100644 --- a/examples/cookbook/testing/widget/finders/pubspec.yaml +++ b/examples/cookbook/testing/widget/finders/pubspec.yaml @@ -3,7 +3,7 @@ description: A new Flutter project. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/widget/introduction/pubspec.yaml b/examples/cookbook/testing/widget/introduction/pubspec.yaml index dd9759ceed..d5d3c4ecd0 100644 --- a/examples/cookbook/testing/widget/introduction/pubspec.yaml +++ b/examples/cookbook/testing/widget/introduction/pubspec.yaml @@ -3,7 +3,7 @@ description: Widget testing example snippets. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/widget/orientation_tests/pubspec.yaml b/examples/cookbook/testing/widget/orientation_tests/pubspec.yaml index 4c8c062bff..6f873bd68d 100644 --- a/examples/cookbook/testing/widget/orientation_tests/pubspec.yaml +++ b/examples/cookbook/testing/widget/orientation_tests/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/widget/scrolling/pubspec.yaml b/examples/cookbook/testing/widget/scrolling/pubspec.yaml index ca0df746d5..e5df2a0574 100644 --- a/examples/cookbook/testing/widget/scrolling/pubspec.yaml +++ b/examples/cookbook/testing/widget/scrolling/pubspec.yaml @@ -3,7 +3,7 @@ description: Scrollable widget testing. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/cookbook/testing/widget/tap_drag/pubspec.yaml b/examples/cookbook/testing/widget/tap_drag/pubspec.yaml index 58d20d7d57..0ae2f1e6ad 100644 --- a/examples/cookbook/testing/widget/tap_drag/pubspec.yaml +++ b/examples/cookbook/testing/widget/tap_drag/pubspec.yaml @@ -3,7 +3,7 @@ description: Tap drag widget testing examples. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/data-and-backend/json/pubspec.yaml b/examples/data-and-backend/json/pubspec.yaml index 00141908ad..dadd5faedb 100644 --- a/examples/data-and-backend/json/pubspec.yaml +++ b/examples/data-and-backend/json/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: json_annotation: ^4.9.0 diff --git a/examples/deployment/obfuscate/pubspec.yaml b/examples/deployment/obfuscate/pubspec.yaml index 42c1894533..2eaa2f5b5a 100644 --- a/examples/deployment/obfuscate/pubspec.yaml +++ b/examples/deployment/obfuscate/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/fwe/birdle/.gitignore b/examples/fwe/birdle/.gitignore new file mode 100644 index 0000000000..3820a95c65 --- /dev/null +++ b/examples/fwe/birdle/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/examples/fwe/birdle/.metadata b/examples/fwe/birdle/.metadata new file mode 100644 index 0000000000..c9627623ee --- /dev/null +++ b/examples/fwe/birdle/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "e308d690a1193ea0d93d62aad6efe48e5994bc3c" + channel: "[user-branch]" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + base_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + - platform: android + create_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + base_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + - platform: ios + create_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + base_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + - platform: linux + create_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + base_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + - platform: macos + create_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + base_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + - platform: web + create_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + base_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + - platform: windows + create_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + base_revision: e308d690a1193ea0d93d62aad6efe48e5994bc3c + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/examples/fwe/birdle/README.md b/examples/fwe/birdle/README.md new file mode 100644 index 0000000000..4ff8a5029e --- /dev/null +++ b/examples/fwe/birdle/README.md @@ -0,0 +1,3 @@ +# birdle + +A new Flutter project. diff --git a/examples/fwe/birdle/analysis_options.yaml b/examples/fwe/birdle/analysis_options.yaml new file mode 100644 index 0000000000..f9b303465f --- /dev/null +++ b/examples/fwe/birdle/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/examples/fwe/birdle/lib/game.dart b/examples/fwe/birdle/lib/game.dart new file mode 100644 index 0000000000..394ec65eef --- /dev/null +++ b/examples/fwe/birdle/lib/game.dart @@ -0,0 +1,218 @@ +import 'dart:collection'; +import 'dart:math'; + +const List allLegalGuesses = [...legalWords, ...legalGuesses]; +const defaultNumGuesses = 5; + +enum HitType { none, hit, partial, miss, removed } + +typedef Letter = ({String char, HitType type}); + +const legalWords = ["aback", "abase", "abate", "abbey", "abbot"]; + +/// Legal guesses minus legal wordles +const legalGuesses = [ + "aback", + "abase", + "abate", + "abbey", + "abbot", + "abhor", + "abide", + "abled", + "abode", + "abort", +]; + +/// This class holds game state for a single round of Bulls and Cows, +/// and exposes methods that a UI would need to manage the game state. +/// +/// On it's own, this class won't manage a game. For example, it won't +/// call [startGame] on it's own. It assumes that a client will use it's +/// methods to progress through a game. +class Game { + Game({this.numAllowedGuesses = defaultNumGuesses, this.seed}) { + _wordToGuess = seed == null ? Word.random() : Word.fromSeed(seed!); + _guesses = List.filled(numAllowedGuesses, Word.empty()); + } + + late final int numAllowedGuesses; + late List _guesses; + late Word _wordToGuess; + int? seed; + + Word get hiddenWord => _wordToGuess; + + UnmodifiableListView get guesses => UnmodifiableListView(_guesses); + + Word get previousGuess { + final index = _guesses.lastIndexWhere((word) => word.isNotEmpty); + return index == -1 ? Word.empty() : _guesses[index]; + } + + int get activeIndex { + return _guesses.indexWhere((word) => word.isEmpty); + } + + int get guessesRemaining { + if (activeIndex == -1) return 0; + return numAllowedGuesses - activeIndex; + } + + void resetGame() { + _wordToGuess = seed == null ? Word.random() : Word.fromSeed(seed!); + _guesses = List.filled(numAllowedGuesses, Word.empty()); + } + + // Most common entry-point for handling guess logic. + // For finer control over logic, use other methods such as [isGuessLegal] + // and [matchGuess] + Word guess(String guess) { + final result = matchGuessOnly(guess); + addGuessToList(result); + return result; + } + + bool get didWin { + if (_guesses.first.isEmpty) return false; + + for (var letter in previousGuess) { + if (letter.type != HitType.hit) return false; + } + + return true; + } + + bool get didLose => guessesRemaining == 0 && !didWin; + + // UIs can call this method before calling [guess] if they want + // to show users messages based incorrect words + bool isLegalGuess(String guess) { + return Word.fromString(guess).isLegalGuess; + } + + // Doesn't move the game forward, only executes match logic. + Word matchGuessOnly(String guess) { + // The hidden word will be used by subsequent guesses. + var hiddenCopy = Word.fromString(_wordToGuess.toString()); + return Word.fromString(guess).evaluateGuess(hiddenCopy); + } + + void addGuessToList(Word guess) { + final i = _guesses.indexWhere((word) => word.isEmpty); + _guesses[i] = guess; + } +} + +class Word with IterableMixin { + Word(this._letters); + + factory Word.empty() { + return Word(List.filled(5, (char: '', type: HitType.none))); + } + + factory Word.fromString(String guess) { + var list = guess.toLowerCase().split(''); + var letters = list + .map((String char) => (char: char, type: HitType.none)) + .toList(); + return Word(letters); + } + + factory Word.random() { + var rand = Random(); + var nextWord = legalWords[rand.nextInt(legalWords.length)]; + return Word.fromString(nextWord); + } + + factory Word.fromSeed(int seed) { + return Word.fromString(legalWords[seed % legalWords.length]); + } + + final List _letters; + + /// Loop over the Letters in this word + @override + Iterator get iterator => _letters.iterator; + + @override + bool get isEmpty { + return every((letter) => letter.char.isEmpty); + } + + @override + bool get isNotEmpty => !isEmpty; + + Letter operator [](int i) => _letters[i]; + void operator []=(int i, Letter value) => _letters[i] = value; + + @override + String toString() { + return _letters.map((Letter c) => c.char).join().trim(); + } + + // Used to play game in the CLI implementation + String toStringVerbose() { + return _letters.map((l) => '${l.char} - ${l.type.name}').join('\n'); + } +} + +// Domain specific methods that contain word related logic. +extension WordUtils on Word { + bool get isLegalGuess { + if (!allLegalGuesses.contains(toString())) { + return false; + } + + return true; + } + + /// Compares two [Word] objects and returns a new [Word] that + /// has the same letters as the [this], but each [Letter] + /// has new a [HitType] of either [HitType.hit], + /// [HitType.partial], or [HitType.miss]. + Word evaluateGuess(Word other) { + assert(isLegalGuess); + + // Find exact hits. Mark them as hits, and mark letters in the hidden word + // as removed. + for (var i = 0; i < length; i++) { + if (other[i].char == this[i].char) { + this[i] = (char: this[i].char, type: HitType.hit); + other[i] = (char: other[i].char, type: HitType.removed); + } + } + + // Find the partial matches + // The outer loop is through the hidden word + for (var i = 0; i < other.length; i++) { + // If a letter in the hidden word is already marked as "removed", + // then it's already an exact match, so skip it + Letter targetLetter = other[i]; + if (targetLetter.type != HitType.none) continue; + + // loop through the guessed word onces for each letter in the hidden word + for (var j = 0; j < length; j++) { + Letter guessedLetter = this[j]; + // skip letters that have already been marked as exact matches + if (guessedLetter.type != HitType.none) continue; + // If this letter, which must not be in the same position, is the same, + // it's a partial match + if (guessedLetter.char == targetLetter.char) { + this[j] = (char: guessedLetter.char, type: HitType.partial); + other[i] = (char: targetLetter.char, type: HitType.removed); + break; + } + } + } + + // Mark remaining letters in guessed word as misses + for (var i = 0; i < length; i++) { + if (this[i].type == HitType.none) { + this[i] = (char: this[i].char, type: HitType.miss); + } + } + + return this; + } +} diff --git a/examples/fwe/birdle/lib/main.dart b/examples/fwe/birdle/lib/main.dart new file mode 100644 index 0000000000..f059d0ba04 --- /dev/null +++ b/examples/fwe/birdle/lib/main.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'game.dart'; + +void main() { + runApp(const MainApp()); +} + +// #docregion MainApp +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: Align(alignment: Alignment.centerLeft, child: Text('Birdle')), + ), + body: Center(child: GamePage()), + ), + ); + } +} +// #enddocregion MainApp + +// #docregion GamePage +class GamePage extends StatefulWidget { + const GamePage({super.key}); + + @override + State createState() => _GamePageState(); +} + +class _GamePageState extends State { + final Game _game = Game(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + spacing: 5.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + for (var guess in _game.guesses) + Row( + spacing: 5.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + for (var letter in guess) Tile(letter.char, letter.type), + ], + ), + GuessInput( + onSubmitGuess: (String guess) { + setState(() { + _game.guess(guess); + }); + }, + ), + ], + ), + ); + } +} +// #enddocregion GamePage + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + + void _onSubmit() { + onSubmitGuess(_textEditingController.text.trim()); + _textEditingController.clear(); + _focusNode.requestFocus(); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 250, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + controller: _textEditingController, + autofocus: true, + focusNode: _focusNode, + onSubmitted: (String input) { + _onSubmit(); + }, + ), + ), + ), + IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.arrow_circle_up), + onPressed: _onSubmit, + ), + ], + ); + } +} +// #enddocregion GuessInput + +// #docregion Tile +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + + final String letter; + final HitType hitType; + + @override + Widget build(BuildContext context) { + // #docregion AnimatedContainer + return AnimatedContainer( + duration: const Duration(milliseconds: 500), + // #docregion Curve + curve: Curves.bounceIn, // NEW + // #enddocregion Curve + height: 60, + width: 60, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + color: switch (hitType) { + HitType.hit => Colors.green, + HitType.partial => Colors.yellow, + HitType.miss => Colors.grey, + _ => Colors.white, + }, + ), + child: Center( + child: Text( + letter.toUpperCase(), + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ); + // #enddocregion AnimatedContainer + } +} + +// #enddocregion Tile diff --git a/examples/fwe/birdle/lib/step1_main.dart b/examples/fwe/birdle/lib/step1_main.dart new file mode 100644 index 0000000000..a2dd8274a3 --- /dev/null +++ b/examples/fwe/birdle/lib/step1_main.dart @@ -0,0 +1,28 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion main +void main() { + runApp(const MainApp()); +} +// #enddocregion main + +// #docregion MainApp +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold( + body: Center( + // #docregion Text + child: Text('Hello World!'), + // #enddocregion Text + ), + ), + ); + } +} + +// #enddocregion MainApp diff --git a/examples/fwe/birdle/lib/step2_main.dart b/examples/fwe/birdle/lib/step2_main.dart new file mode 100644 index 0000000000..c6e622a7d7 --- /dev/null +++ b/examples/fwe/birdle/lib/step2_main.dart @@ -0,0 +1,78 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +void main() { + runApp(const MainApp()); +} + +// #docregion MainApp +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold( + body: Center( + // #docregion TileInstance + child: Tile('A', HitType.hit), // NEW + // #enddocregion TileInstance + ), + ), + ); + } +} +// #enddocregion MainApp + +// #docregion Tile +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + + final String letter; + final HitType hitType; + + @override + Widget build(BuildContext context) { + // #docregion Tile-Container + return Container( + width: 60, + height: 60, + // #docregion Tile-BoxDecoration + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + // #docregion Tile-Colors + color: switch (hitType) { + HitType.hit => Colors.green, + HitType.partial => Colors.yellow, + HitType.miss => Colors.grey, + _ => Colors.white, + }, + // #enddocregion Tile-Colors + ), + // #enddocregion Tile-BoxDecoration + // #docregion Tile-Child + child: Center( + child: Text( + letter.toUpperCase(), + style: Theme.of(context).textTheme.titleLarge, + ), + ), + // #enddocregion Tile-Child + ); + // #enddocregion Tile-Container + } +} + +// #enddocregion Tile +void docRegionTileUsage() { + // #docregion TileUsage + // main.dart line ~16 + // green + Tile('A', HitType.hit); + // grey + Tile('A', HitType.miss); + // yellow + Tile('A', HitType.partial); + // #enddocregion TileUsage +} diff --git a/examples/fwe/birdle/lib/step2a_main.dart b/examples/fwe/birdle/lib/step2a_main.dart new file mode 100644 index 0000000000..cd17708632 --- /dev/null +++ b/examples/fwe/birdle/lib/step2a_main.dart @@ -0,0 +1,18 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion Tile +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + + final String letter; + final HitType hitType; + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +// #enddocregion Tile diff --git a/examples/fwe/birdle/lib/step2b_main.dart b/examples/fwe/birdle/lib/step2b_main.dart new file mode 100644 index 0000000000..2a4bbf5bdc --- /dev/null +++ b/examples/fwe/birdle/lib/step2b_main.dart @@ -0,0 +1,19 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion Tile +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + + final String letter; + final HitType hitType; + + @override + Widget build(BuildContext context) { + // TODO: Replace Container with widgets. + return Container(); + } +} + +// #enddocregion Tile diff --git a/examples/fwe/birdle/lib/step2c_main.dart b/examples/fwe/birdle/lib/step2c_main.dart new file mode 100644 index 0000000000..51b8301fcb --- /dev/null +++ b/examples/fwe/birdle/lib/step2c_main.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion Tile +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + + final String letter; + final HitType hitType; + + @override + Widget build(BuildContext context) { + // NEW + return Container( + width: 60, + height: 60, + // TODO: Add needed widgets + ); + } +} + +// #enddocregion Tile diff --git a/examples/fwe/birdle/lib/step2d_main.dart b/examples/fwe/birdle/lib/step2d_main.dart new file mode 100644 index 0000000000..4a99d110c8 --- /dev/null +++ b/examples/fwe/birdle/lib/step2d_main.dart @@ -0,0 +1,26 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion Tile +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + + final String letter; + final HitType hitType; + + @override + Widget build(BuildContext context) { + // NEW + return Container( + width: 60, + height: 60, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + // TODO: add background color + ), + ); + } +} + +// #enddocregion Tile diff --git a/examples/fwe/birdle/lib/step2e_main.dart b/examples/fwe/birdle/lib/step2e_main.dart new file mode 100644 index 0000000000..f57ebad773 --- /dev/null +++ b/examples/fwe/birdle/lib/step2e_main.dart @@ -0,0 +1,31 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion Tile +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + + final String letter; + final HitType hitType; + + @override + Widget build(BuildContext context) { + return Container( + width: 60, + height: 60, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + color: switch (hitType) { + HitType.hit => Colors.green, + HitType.partial => Colors.yellow, + HitType.miss => Colors.grey, + _ => Colors.white, + }, + // TODO: add children + ), + ); + } +} + +// #enddocregion Tile diff --git a/examples/fwe/birdle/lib/step2f_main.dart b/examples/fwe/birdle/lib/step2f_main.dart new file mode 100644 index 0000000000..d564ba9f2b --- /dev/null +++ b/examples/fwe/birdle/lib/step2f_main.dart @@ -0,0 +1,36 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion Tile +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + + final String letter; + final HitType hitType; + + @override + Widget build(BuildContext context) { + return Container( + width: 60, + height: 60, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + color: switch (hitType) { + HitType.hit => Colors.green, + HitType.partial => Colors.yellow, + HitType.miss => Colors.grey, + _ => Colors.white, + }, + ), + child: Center( + child: Text( + letter.toUpperCase(), + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ); + } +} + +// #enddocregion Tile diff --git a/examples/fwe/birdle/lib/step3_main.dart b/examples/fwe/birdle/lib/step3_main.dart new file mode 100644 index 0000000000..50660e0bb1 --- /dev/null +++ b/examples/fwe/birdle/lib/step3_main.dart @@ -0,0 +1,71 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; +import 'step2_main.dart' + show Tile; // Use Tile from step2 for simplicity, or just define it here. + +// But actually the excerpt tool just reads the file, so redefining `Tile` is fine, we just don't tag it. +class Tile extends StatelessWidget { + const Tile(this.letter, this.hitType, {super.key}); + final String letter; + final HitType hitType; + @override + Widget build(BuildContext context) { + return Container(); // Omitted for brevity since it's not tagged here. + } +} + +// #docregion MainApp +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + // #docregion AppBar + appBar: AppBar( + title: Align(alignment: Alignment.centerLeft, child: Text('Birdle')), + ), + // #enddocregion AppBar + body: Center(child: GamePage()), + ), + ); + } +} +// #enddocregion MainApp + +// #docregion GamePage +class GamePage extends StatelessWidget { + GamePage({super.key}); + + // This manages game logic, and is out of scope for this lesson. + final Game _game = Game(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + // #docregion Column + child: Column( + spacing: 5.0, + children: [ + // #docregion Rows + for (var guess in _game.guesses) + Row( + spacing: 5.0, + children: [ + // #docregion TileLoop + for (var letter in guess) Tile(letter.char, letter.type), + // #enddocregion TileLoop + ], + ), + // #enddocregion Rows + ], + ), + // #enddocregion Column + ); + } +} + +// #enddocregion GamePage diff --git a/examples/fwe/birdle/lib/step3a_main.dart b/examples/fwe/birdle/lib/step3a_main.dart new file mode 100644 index 0000000000..436fe7e73e --- /dev/null +++ b/examples/fwe/birdle/lib/step3a_main.dart @@ -0,0 +1,19 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion GamePage +class GamePage extends StatelessWidget { + GamePage({super.key}); + // This object is part of the game.dart file. + // It manages wordle logic, and is outside the scope of this tutorial. + final Game _game = Game(); + + @override + Widget build(BuildContext context) { + // TODO: Replace with screen contents + return Container(); + } +} + +// #enddocregion GamePage diff --git a/examples/fwe/birdle/lib/step3b_main.dart b/examples/fwe/birdle/lib/step3b_main.dart new file mode 100644 index 0000000000..25945c8218 --- /dev/null +++ b/examples/fwe/birdle/lib/step3b_main.dart @@ -0,0 +1,25 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion GamePage +class GamePage extends StatelessWidget { + GamePage({super.key}); + // This manages game logic, and is out of scope for this lesson. + final Game _game = Game(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + spacing: 5.0, + children: [ + // Add children next. + ], + ), + ); + } +} + +// #enddocregion GamePage diff --git a/examples/fwe/birdle/lib/step3c_main.dart b/examples/fwe/birdle/lib/step3c_main.dart new file mode 100644 index 0000000000..6e1e1fd7f5 --- /dev/null +++ b/examples/fwe/birdle/lib/step3c_main.dart @@ -0,0 +1,31 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; + +// #docregion GamePage +class GamePage extends StatelessWidget { + GamePage({super.key}); + // This manages game logic, and is out of scope for this lesson. + final Game _game = Game(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + spacing: 5.0, + children: [ + for (var guess in _game.guesses) + Row( + spacing: 5.0, + children: [ + // We'll add the tiles here later. + ], + ), + ], + ), + ); + } +} + +// #enddocregion GamePage diff --git a/examples/fwe/birdle/lib/step4_main.dart b/examples/fwe/birdle/lib/step4_main.dart new file mode 100644 index 0000000000..c471505103 --- /dev/null +++ b/examples/fwe/birdle/lib/step4_main.dart @@ -0,0 +1,95 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; +import 'step2_main.dart' show Tile; + +// #docregion GamePage +class GamePage extends StatelessWidget { + GamePage({super.key}); + final Game _game = Game(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + spacing: 5.0, + children: [ + for (var guess in _game.guesses) + Row( + spacing: 5.0, + children: [ + for (var letter in guess) Tile(letter.char, letter.type), + ], + ), + // #docregion GuessInputInstance + GuessInput( + onSubmitGuess: (_) { + // TODO, handle guess + print(''); // Temporary + }, + ), + // #enddocregion GuessInputInstance + ], + ), + ); + } +} +// #enddocregion GamePage + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + + // #docregion FocusNode + final FocusNode _focusNode = FocusNode(); + + void _onSubmit() { + onSubmitGuess(_textEditingController.text); + _textEditingController.clear(); + _focusNode.requestFocus(); + } + // #enddocregion FocusNode + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + // #docregion TextField + child: TextField( + maxLength: 5, + focusNode: _focusNode, + autofocus: true, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + controller: _textEditingController, + onSubmitted: (String value) { + _onSubmit(); + }, + ), + // #enddocregion TextField + ), + ), + // #docregion IconButton + IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.arrow_circle_up), + onPressed: _onSubmit, + ), + // #enddocregion IconButton + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4a_main.dart b/examples/fwe/birdle/lib/step4a_main.dart new file mode 100644 index 0000000000..ab64c11a1c --- /dev/null +++ b/examples/fwe/birdle/lib/step4a_main.dart @@ -0,0 +1,17 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + @override + Widget build(BuildContext context) { + // You'll build the UI in the next steps. + return Container(); // Placeholder + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4b_main.dart b/examples/fwe/birdle/lib/step4b_main.dart new file mode 100644 index 0000000000..fc19516039 --- /dev/null +++ b/examples/fwe/birdle/lib/step4b_main.dart @@ -0,0 +1,32 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + ), + ), + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4c_main.dart b/examples/fwe/birdle/lib/step4c_main.dart new file mode 100644 index 0000000000..f820aaf92f --- /dev/null +++ b/examples/fwe/birdle/lib/step4c_main.dart @@ -0,0 +1,36 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + // NEW + final TextEditingController _textEditingController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + ), + ), + ), + // + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4d_main.dart b/examples/fwe/birdle/lib/step4d_main.dart new file mode 100644 index 0000000000..0af4c27cc9 --- /dev/null +++ b/examples/fwe/birdle/lib/step4d_main.dart @@ -0,0 +1,35 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + controller: _textEditingController, // NEW + ), + ), + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4e_main.dart b/examples/fwe/birdle/lib/step4e_main.dart new file mode 100644 index 0000000000..0a2ffc790a --- /dev/null +++ b/examples/fwe/birdle/lib/step4e_main.dart @@ -0,0 +1,39 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + controller: _textEditingController, + onSubmitted: (String input) { + // NEW + print(_textEditingController.text); // Temporary + }, + ), + ), + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4f_main.dart b/examples/fwe/birdle/lib/step4f_main.dart new file mode 100644 index 0000000000..9fe2df2740 --- /dev/null +++ b/examples/fwe/birdle/lib/step4f_main.dart @@ -0,0 +1,40 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + controller: _textEditingController, + onSubmitted: (_) { + // UPDATED + print(_textEditingController.text); // Temporary + _textEditingController.clear(); // NEW + }, + ), + ), + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4g_main.dart b/examples/fwe/birdle/lib/step4g_main.dart new file mode 100644 index 0000000000..cb160d8953 --- /dev/null +++ b/examples/fwe/birdle/lib/step4g_main.dart @@ -0,0 +1,40 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + controller: _textEditingController, + autofocus: true, // NEW + onSubmitted: (String input) { + print(input); // Temporary + _textEditingController.clear(); + }, + ), + ), + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4h_main.dart b/examples/fwe/birdle/lib/step4h_main.dart new file mode 100644 index 0000000000..cf2ba7fc25 --- /dev/null +++ b/examples/fwe/birdle/lib/step4h_main.dart @@ -0,0 +1,21 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + + final FocusNode _focusNode = FocusNode(); // NEW + + @override + Widget build(BuildContext context) { + // ... + return Container(); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4i_main.dart b/examples/fwe/birdle/lib/step4i_main.dart new file mode 100644 index 0000000000..222a7005f3 --- /dev/null +++ b/examples/fwe/birdle/lib/step4i_main.dart @@ -0,0 +1,44 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + + final FocusNode _focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + controller: _textEditingController, + autofocus: true, + focusNode: _focusNode, // NEW + onSubmitted: (String input) { + print(input); // Temporary + _textEditingController.clear(); + _focusNode.requestFocus(); // NEW + }, + ), + ), + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4j_main.dart b/examples/fwe/birdle/lib/step4j_main.dart new file mode 100644 index 0000000000..ad05a6cfc9 --- /dev/null +++ b/examples/fwe/birdle/lib/step4j_main.dart @@ -0,0 +1,44 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + + final FocusNode _focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + maxLength: 5, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(35)), + ), + ), + controller: _textEditingController, + autofocus: true, + focusNode: _focusNode, + onSubmitted: (String input) { + onSubmitGuess(_textEditingController.text.trim()); + _textEditingController.clear(); + _focusNode.requestFocus(); + }, + ), + ), + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4k_main.dart b/examples/fwe/birdle/lib/step4k_main.dart new file mode 100644 index 0000000000..38d5a33255 --- /dev/null +++ b/examples/fwe/birdle/lib/step4k_main.dart @@ -0,0 +1,42 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; +import 'step2_main.dart' show Tile; + +// #docregion GamePage +class GamePage extends StatelessWidget { + final Game _game = Game(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + spacing: 5.0, + children: [ + for (var guess in _game.guesses) + Row( + spacing: 5.0, + children: [ + for (var letter in guess) Tile(letter.char, letter.type), + ], + ), + GuessInput( + onSubmitGuess: (String guess) { + // TODO, handle guess + print(guess); // Temporary + }, + ), + ], + ), + ); + } +} + +// #enddocregion GamePage +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + final void Function(String) onSubmitGuess; + @override + Widget build(BuildContext context) => const SizedBox(); +} diff --git a/examples/fwe/birdle/lib/step4l_main.dart b/examples/fwe/birdle/lib/step4l_main.dart new file mode 100644 index 0000000000..63af3c20fd --- /dev/null +++ b/examples/fwe/birdle/lib/step4l_main.dart @@ -0,0 +1,28 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded(child: Container()), + IconButton( + padding: EdgeInsets.zero, + icon: Icon(Icons.arrow_circle_up), + onPressed: null, + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step4m_main.dart b/examples/fwe/birdle/lib/step4m_main.dart new file mode 100644 index 0000000000..55d292182d --- /dev/null +++ b/examples/fwe/birdle/lib/step4m_main.dart @@ -0,0 +1,32 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; + +// #docregion GuessInput +class GuessInput extends StatelessWidget { + GuessInput({super.key, required this.onSubmitGuess}); + + final void Function(String) onSubmitGuess; + + final TextEditingController _textEditingController = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded(child: Container()), + IconButton( + padding: EdgeInsets.zero, + icon: Icon(Icons.arrow_circle_up), + onPressed: () { + onSubmitGuess(_textEditingController.text.trim()); + _textEditingController.clear(); + _focusNode.requestFocus(); + }, + ), + ], + ); + } +} + +// #enddocregion GuessInput diff --git a/examples/fwe/birdle/lib/step5_main.dart b/examples/fwe/birdle/lib/step5_main.dart new file mode 100644 index 0000000000..43b5d59169 --- /dev/null +++ b/examples/fwe/birdle/lib/step5_main.dart @@ -0,0 +1,58 @@ +// ignore_for_file: unused_import, unused_field, unused_local_variable, avoid_print, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, sized_box_for_whitespace +import 'package:flutter/material.dart'; +import 'game.dart'; +import 'step2_main.dart' show Tile; +import 'step4_main.dart' show GuessInput; + +// #docregion GamePage +// #docregion StatefulWidget +class GamePage extends StatefulWidget { + const GamePage({super.key}); + + @override + State createState() => _GamePageState(); +} +// #enddocregion StatefulWidget + +class _GamePageState extends State { + final Game _game = Game(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + for (var guess in _game.guesses) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + for (var letter in guess) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 2.5, + vertical: 2.5, + ), + child: Tile(letter.char, letter.type), + ), + ], + ), + GuessInput( + // #docregion GuessInputInstance + onSubmitGuess: (String guess) { + // #docregion setState + setState(() { + // NEW + _game.guess(guess); + }); + // #enddocregion setState + }, + // #enddocregion GuessInputInstance + ), + ], + ), + ); + } +} + +// #enddocregion GamePage diff --git a/examples/fwe/birdle/pubspec.yaml b/examples/fwe/birdle/pubspec.yaml new file mode 100644 index 0000000000..026c3b982d --- /dev/null +++ b/examples/fwe/birdle/pubspec.yaml @@ -0,0 +1,19 @@ +name: birdle +description: "A new Flutter project." +publish_to: 'none' +version: 0.1.0+1 + +environment: + sdk: ^3.11.0 + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + uses-material-design: true diff --git a/examples/get-started/flutter-for/android_devs/pubspec.yaml b/examples/get-started/flutter-for/android_devs/pubspec.yaml index 5e00dd10d6..d7571df761 100644 --- a/examples/get-started/flutter-for/android_devs/pubspec.yaml +++ b/examples/get-started/flutter-for/android_devs/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/get-started/flutter-for/declarative/pubspec.yaml b/examples/get-started/flutter-for/declarative/pubspec.yaml index f13e13ade0..426207fca2 100644 --- a/examples/get-started/flutter-for/declarative/pubspec.yaml +++ b/examples/get-started/flutter-for/declarative/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/get-started/flutter-for/ios_devs/lib/grid.dart b/examples/get-started/flutter-for/ios_devs/lib/grid.dart index d279c906f2..3c2889ae84 100644 --- a/examples/get-started/flutter-for/ios_devs/lib/grid.dart +++ b/examples/get-started/flutter-for/ios_devs/lib/grid.dart @@ -15,7 +15,7 @@ class App extends StatelessWidget { } // #docregion grid-example -const widgets = [ +const widgets = [ Text('Row 1'), Icon(CupertinoIcons.arrow_down_square), Icon(CupertinoIcons.arrow_up_square), diff --git a/examples/get-started/flutter-for/ios_devs/lib/navigation.dart b/examples/get-started/flutter-for/ios_devs/lib/navigation.dart index ecffc29eef..c791bc83fa 100644 --- a/examples/get-started/flutter-for/ios_devs/lib/navigation.dart +++ b/examples/get-started/flutter-for/ios_devs/lib/navigation.dart @@ -33,8 +33,8 @@ class Person { const Person({required this.name, required this.age}); } -// Next, create a list of 100 Persons. -final mockPersons = Iterable.generate( +// Next, create a list of 100 persons. +final List mockPersons = List.generate( 100, (index) => Person(name: 'Person #${index + 1}', age: 10 + index), ); diff --git a/examples/get-started/flutter-for/ios_devs/lib/popback.dart b/examples/get-started/flutter-for/ios_devs/lib/popback.dart index ee858040d2..f9c75d544d 100644 --- a/examples/get-started/flutter-for/ios_devs/lib/popback.dart +++ b/examples/get-started/flutter-for/ios_devs/lib/popback.dart @@ -38,7 +38,7 @@ class Person { } // Next, create a list of 100 persons. -final mockPersons = Iterable.generate( +final List mockPersons = List.generate( 100, (index) => Person(name: 'Person #${index + 1}', age: 10 + index), ); diff --git a/examples/get-started/flutter-for/ios_devs/lib/scroll.dart b/examples/get-started/flutter-for/ios_devs/lib/scroll.dart index 641599014a..41fb91f4a5 100644 --- a/examples/get-started/flutter-for/ios_devs/lib/scroll.dart +++ b/examples/get-started/flutter-for/ios_devs/lib/scroll.dart @@ -40,8 +40,8 @@ class PersonView extends StatelessWidget { } } -// then we create a list of people -final mockPersons = Iterable.generate( +// Next, create a list of 100 persons. +final List mockPersons = List.generate( 100, (index) => Person(name: 'Person #${index + 1}', age: 10 + index), ); diff --git a/examples/get-started/flutter-for/ios_devs/pubspec.yaml b/examples/get-started/flutter-for/ios_devs/pubspec.yaml index ca9b25e3ba..ff56336dac 100644 --- a/examples/get-started/flutter-for/ios_devs/pubspec.yaml +++ b/examples/get-started/flutter-for/ios_devs/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/get-started/flutter-for/react_native_devs/lib/examples.dart b/examples/get-started/flutter-for/react_native_devs/lib/examples.dart index 7068c6ba82..159f4f3321 100644 --- a/examples/get-started/flutter-for/react_native_devs/lib/examples.dart +++ b/examples/get-started/flutter-for/react_native_devs/lib/examples.dart @@ -324,8 +324,8 @@ class HttpExample extends StatefulWidget { class _HttpExampleState extends State { String _ipAddress = ''; // #docregion http - final url = Uri.parse('https://httpbin.org/ip'); - final httpClient = HttpClient(); + final Uri url = Uri.parse('https://httpbin.org/ip'); + final HttpClient httpClient = HttpClient(); Future getIPAddress() async { final request = await httpClient.getUrl(url); diff --git a/examples/get-started/flutter-for/react_native_devs/my_widgets/pubspec.yaml b/examples/get-started/flutter-for/react_native_devs/my_widgets/pubspec.yaml index 15d4471a5e..2f17ed340a 100644 --- a/examples/get-started/flutter-for/react_native_devs/my_widgets/pubspec.yaml +++ b/examples/get-started/flutter-for/react_native_devs/my_widgets/pubspec.yaml @@ -4,4 +4,4 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 diff --git a/examples/get-started/flutter-for/react_native_devs/pubspec.yaml b/examples/get-started/flutter-for/react_native_devs/pubspec.yaml index 66926b1ee5..40e12dcbcd 100644 --- a/examples/get-started/flutter-for/react_native_devs/pubspec.yaml +++ b/examples/get-started/flutter-for/react_native_devs/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 workspace: - my_widgets diff --git a/examples/get-started/flutter-for/web_devs/pubspec.yaml b/examples/get-started/flutter-for/web_devs/pubspec.yaml index 756b3bb779..ecf0bcaa1c 100644 --- a/examples/get-started/flutter-for/web_devs/pubspec.yaml +++ b/examples/get-started/flutter-for/web_devs/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/get-started/flutter-for/xamarin_devs/pubspec.yaml b/examples/get-started/flutter-for/xamarin_devs/pubspec.yaml index 06f17a5854..bf0c113be1 100644 --- a/examples/get-started/flutter-for/xamarin_devs/pubspec.yaml +++ b/examples/get-started/flutter-for/xamarin_devs/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/googleapis/lib/main.dart b/examples/googleapis/lib/main.dart index 3be0172504..981dae7b39 100644 --- a/examples/googleapis/lib/main.dart +++ b/examples/googleapis/lib/main.dart @@ -42,7 +42,7 @@ class _LikedVideosWidgetState extends State<_LikedVideosWidget> { List? _favoriteVideos; // #docregion init - final _googleSignIn = GoogleSignIn.instance; + final GoogleSignIn _googleSignIn = GoogleSignIn.instance; // #docregion post-init @override diff --git a/examples/googleapis/pubspec.yaml b/examples/googleapis/pubspec.yaml index dbe37d8ba1..035d0e01c3 100644 --- a/examples/googleapis/pubspec.yaml +++ b/examples/googleapis/pubspec.yaml @@ -3,13 +3,13 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: collection: any # Pull the version of package:collection from Flutter. extension_google_sign_in_as_googleapis_auth: ^3.0.0 flutter: sdk: flutter - google_sign_in: ^7.1.1 - googleapis: ^15.0.0 + google_sign_in: ^7.2.0 + googleapis: ^16.0.0 http: ^1.5.0 diff --git a/examples/integration_test/pubspec.yaml b/examples/integration_test/pubspec.yaml index 13e225551e..d6156628b4 100644 --- a/examples/integration_test/pubspec.yaml +++ b/examples/integration_test/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.0.1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/integration_test_migration/pubspec.yaml b/examples/integration_test_migration/pubspec.yaml index 6e094c4a42..6015641ad9 100644 --- a/examples/integration_test_migration/pubspec.yaml +++ b/examples/integration_test_migration/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/internationalization/add_language/lib/nn_intl.dart b/examples/internationalization/add_language/lib/nn_intl.dart index cde7ccb005..d02e162fa7 100644 --- a/examples/internationalization/add_language/lib/nn_intl.dart +++ b/examples/internationalization/add_language/lib/nn_intl.dart @@ -65,7 +65,7 @@ const nnLocaleDatePatterns = { /// These are not accurate and are just a clone of the date symbols for the /// `no` locale to demonstrate how one would write and use custom date symbols. // #docregion date-symbols -const nnDateSymbols = { +const Map nnDateSymbols = { 'NAME': 'nn', 'ERAS': ['f.Kr.', 'e.Kr.'], // #enddocregion date-symbols diff --git a/examples/internationalization/add_language/pubspec.yaml b/examples/internationalization/add_language/pubspec.yaml index 71f6cf3387..0243772c46 100644 --- a/examples/internationalization/add_language/pubspec.yaml +++ b/examples/internationalization/add_language/pubspec.yaml @@ -3,7 +3,7 @@ description: An i18n app example that adds a supported language resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/internationalization/gen_l10n_example/pubspec.yaml b/examples/internationalization/gen_l10n_example/pubspec.yaml index ccef77f100..f30b1e4d4a 100644 --- a/examples/internationalization/gen_l10n_example/pubspec.yaml +++ b/examples/internationalization/gen_l10n_example/pubspec.yaml @@ -5,7 +5,7 @@ description: >- resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 # #docregion flutter-localizations dependencies: diff --git a/examples/internationalization/intl_example/pubspec.yaml b/examples/internationalization/intl_example/pubspec.yaml index f0b3563d5a..c267678a67 100644 --- a/examples/internationalization/intl_example/pubspec.yaml +++ b/examples/internationalization/intl_example/pubspec.yaml @@ -3,7 +3,7 @@ description: Example of a Flutter app using the intl library services. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/internationalization/minimal/pubspec.yaml b/examples/internationalization/minimal/pubspec.yaml index 014f312acd..eb845ff4f2 100644 --- a/examples/internationalization/minimal/pubspec.yaml +++ b/examples/internationalization/minimal/pubspec.yaml @@ -3,7 +3,7 @@ description: A minimal example of a localized Flutter app resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/base/pubspec.yaml b/examples/layout/base/pubspec.yaml index aeac66d931..e430dad6b4 100644 --- a/examples/layout/base/pubspec.yaml +++ b/examples/layout/base/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/card_and_stack/pubspec.yaml b/examples/layout/card_and_stack/pubspec.yaml index 3b1460f11a..f855ee91cd 100644 --- a/examples/layout/card_and_stack/pubspec.yaml +++ b/examples/layout/card_and_stack/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/constraints/lib/main.dart b/examples/layout/constraints/lib/main.dart index eb1a2e7336..057b113e89 100644 --- a/examples/layout/constraints/lib/main.dart +++ b/examples/layout/constraints/lib/main.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; void main() => runApp(const HomePage()); -const red = Colors.red; -const green = Colors.green; -const blue = Colors.blue; -const big = TextStyle(fontSize: 30); +const Color red = Colors.red; +const Color green = Colors.green; +const Color blue = Colors.blue; +const TextStyle big = TextStyle(fontSize: 30); ////////////////////////////////////////////////// diff --git a/examples/layout/constraints/pubspec.yaml b/examples/layout/constraints/pubspec.yaml index fec894af0b..6df5d7b51a 100644 --- a/examples/layout/constraints/pubspec.yaml +++ b/examples/layout/constraints/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/container/pubspec.yaml b/examples/layout/container/pubspec.yaml index 571cd67faf..e7eaa85278 100644 --- a/examples/layout/container/pubspec.yaml +++ b/examples/layout/container/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/gallery/pubspec.yaml b/examples/layout/gallery/pubspec.yaml index 753756131e..5e63b7e161 100644 --- a/examples/layout/gallery/pubspec.yaml +++ b/examples/layout/gallery/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: animations: ^2.0.11 diff --git a/examples/layout/grid_and_list/pubspec.yaml b/examples/layout/grid_and_list/pubspec.yaml index d64a4df225..8e0179ee44 100644 --- a/examples/layout/grid_and_list/pubspec.yaml +++ b/examples/layout/grid_and_list/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/lakes/interactive/pubspec.yaml b/examples/layout/lakes/interactive/pubspec.yaml index acf31e7a7e..989669353a 100644 --- a/examples/layout/lakes/interactive/pubspec.yaml +++ b/examples/layout/lakes/interactive/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/lakes/step2/pubspec.yaml b/examples/layout/lakes/step2/pubspec.yaml index c9b72d0348..ac5bdd470a 100644 --- a/examples/layout/lakes/step2/pubspec.yaml +++ b/examples/layout/lakes/step2/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/lakes/step3/pubspec.yaml b/examples/layout/lakes/step3/pubspec.yaml index 527c6ceb94..0f6d17dded 100644 --- a/examples/layout/lakes/step3/pubspec.yaml +++ b/examples/layout/lakes/step3/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/lakes/step4/pubspec.yaml b/examples/layout/lakes/step4/pubspec.yaml index 106faf36bc..d331ae3b05 100644 --- a/examples/layout/lakes/step4/pubspec.yaml +++ b/examples/layout/lakes/step4/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/lakes/step5/pubspec.yaml b/examples/layout/lakes/step5/pubspec.yaml index 68a9bf4b1f..ce0445c4af 100644 --- a/examples/layout/lakes/step5/pubspec.yaml +++ b/examples/layout/lakes/step5/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/lakes/step6/pubspec.yaml b/examples/layout/lakes/step6/pubspec.yaml index 61e24485ad..930535164b 100644 --- a/examples/layout/lakes/step6/pubspec.yaml +++ b/examples/layout/lakes/step6/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/non_material/pubspec.yaml b/examples/layout/non_material/pubspec.yaml index 4aec841fe8..76d2b91d09 100644 --- a/examples/layout/non_material/pubspec.yaml +++ b/examples/layout/non_material/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/pavlova/pubspec.yaml b/examples/layout/pavlova/pubspec.yaml index 870039cc3e..e968a1a306 100644 --- a/examples/layout/pavlova/pubspec.yaml +++ b/examples/layout/pavlova/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/row_column/pubspec.yaml b/examples/layout/row_column/pubspec.yaml index 75da5d49f4..97630e1095 100644 --- a/examples/layout/row_column/pubspec.yaml +++ b/examples/layout/row_column/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/layout/sizing/pubspec.yaml b/examples/layout/sizing/pubspec.yaml index 752ebc9417..490a2aae36 100644 --- a/examples/layout/sizing/pubspec.yaml +++ b/examples/layout/sizing/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/perf/concurrency/isolates/pubspec.yaml b/examples/perf/concurrency/isolates/pubspec.yaml index 83a8d8afe2..1765394076 100644 --- a/examples/perf/concurrency/isolates/pubspec.yaml +++ b/examples/perf/concurrency/isolates/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/perf/deferred_components/pubspec.yaml b/examples/perf/deferred_components/pubspec.yaml index 8cebecc54f..086db23498 100644 --- a/examples/perf/deferred_components/pubspec.yaml +++ b/examples/perf/deferred_components/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/platform_integration/compose_activities/pubspec.yaml b/examples/platform_integration/compose_activities/pubspec.yaml index 54257e9f10..eff123fc1c 100644 --- a/examples/platform_integration/compose_activities/pubspec.yaml +++ b/examples/platform_integration/compose_activities/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/platform_integration/pigeon/pubspec.yaml b/examples/platform_integration/pigeon/pubspec.yaml index e3fdb448da..61bb624e33 100644 --- a/examples/platform_integration/pigeon/pubspec.yaml +++ b/examples/platform_integration/pigeon/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/platform_integration/platform_channels/pubspec.yaml b/examples/platform_integration/platform_channels/pubspec.yaml index f04afb5548..e6c350f14b 100644 --- a/examples/platform_integration/platform_channels/pubspec.yaml +++ b/examples/platform_integration/platform_channels/pubspec.yaml @@ -9,7 +9,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/platform_integration/platform_views/pubspec.yaml b/examples/platform_integration/platform_views/pubspec.yaml index 3437282be3..f697fca2e2 100644 --- a/examples/platform_integration/platform_views/pubspec.yaml +++ b/examples/platform_integration/platform_views/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/platform_integration/plugin_api_migration/pubspec.yaml b/examples/platform_integration/plugin_api_migration/pubspec.yaml index 7db4d3fcb4..73229464a8 100644 --- a/examples/platform_integration/plugin_api_migration/pubspec.yaml +++ b/examples/platform_integration/plugin_api_migration/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/pubspec.yaml b/examples/pubspec.yaml index 84c09df27e..23ab460067 100644 --- a/examples/pubspec.yaml +++ b/examples/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_docs_examples publish_to: none environment: - sdk: ^3.10.0 + sdk: ^3.11.0 workspace: - _animation/basic_hero_animation diff --git a/examples/resources/architectural_overview/pubspec.yaml b/examples/resources/architectural_overview/pubspec.yaml index d6435a57d0..c598856dc0 100644 --- a/examples/resources/architectural_overview/pubspec.yaml +++ b/examples/resources/architectural_overview/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/resources/dart_swift_concurrency/pubspec.yaml b/examples/resources/dart_swift_concurrency/pubspec.yaml index 167733984d..1f35a42ac3 100644 --- a/examples/resources/dart_swift_concurrency/pubspec.yaml +++ b/examples/resources/dart_swift_concurrency/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/state_mgmt/simple/pubspec.yaml b/examples/state_mgmt/simple/pubspec.yaml index d1e5bf7bcf..294557d15a 100644 --- a/examples/state_mgmt/simple/pubspec.yaml +++ b/examples/state_mgmt/simple/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/testing/code_debugging/pubspec.yaml b/examples/testing/code_debugging/pubspec.yaml index 188cafe886..a834e893a8 100644 --- a/examples/testing/code_debugging/pubspec.yaml +++ b/examples/testing/code_debugging/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/testing/common_errors/pubspec.yaml b/examples/testing/common_errors/pubspec.yaml index fd7e42e98e..ef32a56a21 100644 --- a/examples/testing/common_errors/pubspec.yaml +++ b/examples/testing/common_errors/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/testing/errors/pubspec.yaml b/examples/testing/errors/pubspec.yaml index 55db60b25d..54cd00fc0f 100644 --- a/examples/testing/errors/pubspec.yaml +++ b/examples/testing/errors/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/testing/integration_tests/how_to/pubspec.yaml b/examples/testing/integration_tests/how_to/pubspec.yaml index 1afe013f60..61c34c4669 100644 --- a/examples/testing/integration_tests/how_to/pubspec.yaml +++ b/examples/testing/integration_tests/how_to/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/testing/native_debugging/pubspec.yaml b/examples/testing/native_debugging/pubspec.yaml index 2886d224dd..6483cd8472 100644 --- a/examples/testing/native_debugging/pubspec.yaml +++ b/examples/testing/native_debugging/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/tools/lib/hot-reload/after.dart b/examples/tools/lib/hot-reload/after.dart index 29a5755ef9..9a0f973bc6 100644 --- a/examples/tools/lib/hot-reload/after.dart +++ b/examples/tools/lib/hot-reload/after.dart @@ -1,4 +1,4 @@ -// ignore_for_file: avoid_print, prefer_const_declarations +// ignore_for_file: specify_nonobvious_property_types, avoid_print, prefer_const_declarations import 'package:flutter/material.dart'; diff --git a/examples/tools/lib/hot-reload/before.dart b/examples/tools/lib/hot-reload/before.dart index 4d6b419afa..d0ee66ac61 100644 --- a/examples/tools/lib/hot-reload/before.dart +++ b/examples/tools/lib/hot-reload/before.dart @@ -1,4 +1,4 @@ -// ignore_for_file: avoid_print, prefer_const_declarations, prefer_const_constructors +// ignore_for_file: specify_nonobvious_property_types, avoid_print, prefer_const_declarations, prefer_const_constructors // #docregion build import 'package:flutter/material.dart'; diff --git a/examples/tools/lib/hot-reload/foo_const.dart b/examples/tools/lib/hot-reload/foo_const.dart index 8b59f2c1a9..fd183a5f5c 100644 --- a/examples/tools/lib/hot-reload/foo_const.dart +++ b/examples/tools/lib/hot-reload/foo_const.dart @@ -1,4 +1,4 @@ -// ignore_for_file: avoid_print +// ignore_for_file: specify_nonobvious_property_types, avoid_print // #docregion const const foo = 1; diff --git a/examples/tools/pubspec.yaml b/examples/tools/pubspec.yaml index 8508849444..f448444407 100644 --- a/examples/tools/pubspec.yaml +++ b/examples/tools/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/ui/actions_and_shortcuts/pubspec.yaml b/examples/ui/actions_and_shortcuts/pubspec.yaml index b17f8ab2ea..e373510d40 100644 --- a/examples/ui/actions_and_shortcuts/pubspec.yaml +++ b/examples/ui/actions_and_shortcuts/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/ui/adaptive_app_demos/pubspec.yaml b/examples/ui/adaptive_app_demos/pubspec.yaml index c1b1bb794a..7a466a4e1d 100644 --- a/examples/ui/adaptive_app_demos/pubspec.yaml +++ b/examples/ui/adaptive_app_demos/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/ui/assets_and_images/pubspec.yaml b/examples/ui/assets_and_images/pubspec.yaml index 25a5354e19..31e8cc3542 100644 --- a/examples/ui/assets_and_images/pubspec.yaml +++ b/examples/ui/assets_and_images/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/ui/focus/pubspec.yaml b/examples/ui/focus/pubspec.yaml index 05756969cd..2c13423351 100644 --- a/examples/ui/focus/pubspec.yaml +++ b/examples/ui/focus/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/ui/interactive/pubspec.yaml b/examples/ui/interactive/pubspec.yaml index eb8e66d4e6..65d4a388d0 100644 --- a/examples/ui/interactive/pubspec.yaml +++ b/examples/ui/interactive/pubspec.yaml @@ -3,7 +3,7 @@ description: Sample code for interactive.md resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/ui/navigation/pubspec.yaml b/examples/ui/navigation/pubspec.yaml index 64d247ce82..442d5d683f 100644 --- a/examples/ui/navigation/pubspec.yaml +++ b/examples/ui/navigation/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/ui/widgets_intro/pubspec.yaml b/examples/ui/widgets_intro/pubspec.yaml index 9feac8c2e2..3fd65854b9 100644 --- a/examples/ui/widgets_intro/pubspec.yaml +++ b/examples/ui/widgets_intro/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/examples/visual_debugging/pubspec.yaml b/examples/visual_debugging/pubspec.yaml index bbf4a76d27..73c889985e 100644 --- a/examples/visual_debugging/pubspec.yaml +++ b/examples/visual_debugging/pubspec.yaml @@ -4,7 +4,7 @@ description: Examples of visual debugging. resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: flutter: diff --git a/firebase.json b/firebase.json index 269a94b89a..738fd0401d 100644 --- a/firebase.json +++ b/firebase.json @@ -89,7 +89,7 @@ { "source": "/platform-services", "destination": "/platform-integration/platform-channels", "type": 301 }, { "source": "/predictive-back", "destination": "/release/breaking-changes/android-predictive-back#migration-guide", "type": 301 }, { "source": "/reading-writing-files", "destination": "/cookbook/persistence/reading-writing-files", "type": 301 }, - { "source": "/reference/tutorials", "destination": "/reference/learning-resources", "type": 301 }, + { "source": "/reference/tutorials", "destination": "/learn/learning-resources", "type": 301 }, { "source": "/release-notes", "destination": "/release/release-notes", "type": 301 }, { "source": "/release-notes/:version*", "destination": "/release/release-notes/release-notes-:version*", "type": 301 }, { "source": "/responsive*", "destination": "/ui/layout/responsive/adaptive-responsive", "type": 301 }, @@ -100,7 +100,7 @@ { "source": "/technical-overview", "destination": "/resources/architectural-overview", "type": 301 }, { "source": "/text-input", "destination": "/cookbook/forms/text-input", "type": 301 }, { "source": "/tutorial", "destination": "/learn/pathway/tutorial", "type": 301 }, - { "source": "/tutorials", "destination": "/reference/learning-resources", "type": 301 }, + { "source": "/tutorials", "destination": "/learn/learning-resources", "type": 301 }, { "source": "/unbounded-constraints", "destination": "/ui/layout/constraints#unbounded", "type": 301 }, { "source": "/ui-performance", "destination": "/perf/ui-performance", "type": 301 }, { "source": "/ui/widgets-intro", "destination": "/ui", "type": 301 }, @@ -115,17 +115,18 @@ { "source": "/widgets/:rest*", "destination": "/ui/widgets/:rest*", "type": 301 }, { "source": "/accessibility-and-localization", "destination": "/ui/accessibility", "type": 301 }, + { "source": "/accessibility-and-localization/internationalization", "destination": "/ui/internationalization", "type": 301 }, { "source": "/accessibility-and-localization/:rest*", "destination": "/ui/accessibility", "type": 301 }, { "source": "/add-to-app/android/add-splash-screen", "destination": "/platform-integration/android/splash-screen", "type": 301 }, - { "source": "/codelabs", "destination": "/reference/learning-resources", "type": 301 }, + { "source": "/codelabs", "destination": "/learn/learning-resources", "type": 301 }, { "source": "/codelabs/layout-basics", "destination": "/ui/layout", "type": 301 }, { "source": "/codelabs/explicit-animations", "destination": "/ui/animations/tutorial", "type": 301 }, { "source": "/codelabs/implicit-animations", "destination": "https://codelabs.developers.google.cn/advanced-flutter-animations", "type": 301 }, - { "source": "/cookbook", "destination": "/reference/learning-resources", "type": 301 }, + { "source": "/cookbook", "destination": "/learn/learning-resources", "type": 301 }, { "source": "/cookbook/games/google-mobile-ads", "destination": "/cookbook/plugins/google-mobile-ads", "type": 301 }, { "source": "/cookbook/images/cached-images", "destination": "/cookbook/images/network-image", "type": 301 }, - { "source": "/cookbook/effects/photo-filter-carousel", "destination": "/reference/learning-resources", "type": 301 }, - { "source": "/cookbook/effects/gradient-bubbles", "destination": "/reference/learning-resources", "type": 301 }, + { "source": "/cookbook/effects/photo-filter-carousel", "destination": "/learn/learning-resources", "type": 301 }, + { "source": "/cookbook/effects/gradient-bubbles", "destination": "/learn/learning-resources", "type": 301 }, { "source": "/cookbook/networking/named-routes", "destination": "/cookbook/navigation/named-routes", "type": 301 }, { "source": "/cookbook/testing/integration-test-introduction", "destination": "/cookbook/testing/integration", "type": 301 }, { "source": "/cookbook/testing/integration-test-profiling", "destination": "/cookbook/testing/integration/profiling", "type": 301 }, @@ -149,6 +150,7 @@ { "source": "/get-started/fundamentals/*", "destination": "/learn/pathway", "type": 301 }, { "source": "/get-started/install", "destination": "/install", "type": 301 }, { "source": "/get-started/install/help", "destination": "/install/troubleshoot", "type": 301 }, + { "source": "/get-started/install/*", "destination": "/install", "type": 301 }, { "source": "/get-started/learn-flutter", "destination": "/learn", "type": 301 }, { "source": "/get-started/learn-more", "destination": "/learn", "type": 301 }, { "source": "/get-started/test-drive*", "destination": "/learn/pathway", "type": 301 }, @@ -198,7 +200,7 @@ { "source": "/release/archive", "destination": "/install/archive", "type": 301 }, { "source": "/release/upgrade", "destination": "/install/upgrade", "type": 301 }, { "source": "/resources/ai-overview", "destination": "/ai/create-with-ai", "type": 301 }, - { "source": "/resources/books", "destination": "https://docs.flutter.cn/reference/learning-resources", "type": 301 }, + { "source": "/resources/books", "destination": "/learn/learning-resources", "type": 301 }, { "source": "/resources/bootstrap-into-dart", "destination": "https://dart.cn/learn", "type": 301 }, { "source": "/resources/compatibility", "destination": "/release/compatibility-policy", "type": 301 }, { "source": "/resources/dart-swift-concurrency", "destination": "/flutter-for/dart-swift-concurrency", "type": 301 }, @@ -304,6 +306,7 @@ { "source": "/go/allow-datatable-row-height-to-expand", "destination": "https://docs.google.com/document/d/1qr1IgaU2z9eaM6zCa4u-Q8kJD5Zq7IcdxVyrgR7CwZc/edit?usp=sharing&resourcekey=0-1bNp0ocF4AwGBj6NvRfrEA", "type": 301 }, { "source": "/go/analyze-flutter-in-runtime", "destination": "https://docs.google.com/document/d/1VZOv9NsmNeXaqM9h9EjKTz5cg7lEsA623XBfbojXU5U/edit?usp=sharing", "type": 301 }, { "source": "/go/android-assets-in-flutter", "destination": "https://docs.google.com/document/d/1jymgQYxRTe5rdprt74ERh7Jsa0lfnRuMnkOmJusLWsE/edit", "type": 301 }, + { "source": "/go/android-built-in-kotlin-support", "destination": "https://docs.google.com/document/d/1iAMVVvsHqs130hc3-49sstv7n63TgM5aUd-cqAGWHfc/edit", "type": 301 }, { "source": "/go/android-dependency-versions", "destination": "https://docs.google.com/document/d/1qeeM5QG-jiafttSgvc7yvC19IDRggFFZQTktBVxL6sI/edit?usp=sharing&resourcekey=0-HLEAiBOMxAlQxDs-mEeffw", "type": 301 }, { "source": "/go/android-embedding-dependencies", "destination": "https://docs.google.com/document/d/1vITp2mUZRa-cmll0sPH0zjNgPlyvOMx7awxPNRAPyic/edit", "type": 301 }, { "source": "/go/android-embedding-move", "destination": "https://docs.google.com/document/d/1nQujwZfEe3QOHTyZn160eImpoJOYioADP09DtumcLcc/edit?ts=5d8041db#", "type": 301 }, @@ -383,6 +386,7 @@ { "source": "/go/dash-tooling-plugin-strategy", "destination": "https://docs.google.com/document/d/1Zc0AE8JTKfOSA-IFpEYcPFJ2eALbXE3AG4ZucWXeMig/", "type": 301 }, { "source": "/go/data-sync", "destination": "https://docs.google.com/document/d/1yH96-p-SkMmt6hL5xHHDtMvCKRz2XGrMuw9ZY_nE954", "type": 301 }, { "source": "/go/decouple-design", "destination": "https://docs.google.com/document/d/189AbzVGpxhQczTcdfJd13o_EL36t-M5jOEt1hgBIh7w/edit?usp=sharing", "type": 301 }, + { "source": "/go/decouple-menu-state", "destination": "https://docs.google.com/document/d/1enHNxOzwI1WDC4ZOqQXKAGbBX07nD1yLcgSO8t7lyg8", "type": 301 }, { "source": "/go/decouple-page-transition-builders", "destination": "https://docs.google.com/document/d/1iIoJH_TkeCWl14h_jnVqW5EG-y2y7YKWbnkPYMZUZUo/edit?usp=sharing", "type": 301}, { "source": "/go/decoupling-design-from-text", "destination": "https://docs.google.com/document/d/1oFezK5leJzTWA5lsw3BQGx7gLbhpSL8dMleU3HD7bNY/edit?usp=sharing", "type": 301}, { "source": "/go/dds-daemon", "destination": "https://docs.google.com/document/d/18IgFakijiv9CLFGT5BckbwZuf2pqhOUeN27mB9XqvpQ/edit?usp=sharing&resourcekey=0-rBHvH9gLXLjGPWt5WE-XFg", "type": 301 }, @@ -452,6 +456,7 @@ { "source": "/go/floating-snackbar-offset", "destination": "https://docs.google.com/document/d/1elP-y83PtvfAZHNcpHCtnOFhZO9VnnlobwfQ33QO4hg/edit", "type": 301 }, { "source": "/go/floating-widgetspans", "destination": "https://docs.google.com/document/d/1I-VqCxvszXGAas_6EE9b6ZIzyMY3HjxDxGW9_DWWd7M/edit", "type": 301 }, { "source": "/go/flutter-android-emulator-testing", "destination": "https://docs.google.com/document/d/10wYUcLcSTF4Epg2EUGoBqOkkOe4zxKHvYKjXFZAOgGs/edit?usp=sharing&resourcekey=0-pltjPvEtVezXDADMbUwFHQ", "type": 301 }, + { "source": "/go/flutter-dependency-hosting", "destination": "https://docs.google.com/document/d/1EKb1VypI0ArXqwBWJKGTsiQ2WU0ruYb55JpIeKxWaDs/edit", "type": 301 }, { "source": "/go/flutter-developer-tooling-plugins", "destination": "https://docs.google.com/document/d/189A-T-pcHPuds8pOtbzLS8Wl6Hr0AIAIl0aRTPvLKec/", "type": 301 }, { "source": "/go/flutter-devtools-extensions", "destination": "https://docs.google.com/document/d/189A-T-pcHPuds8pOtbzLS8Wl6Hr0AIAIl0aRTPvLKec/", "type": 301 }, { "source": "/go/flutter-devtools-static-extensions", "destination": "https://docs.google.com/document/d/11J4h1oEfB9wBoRYUy4KzclnKITBY-234jKpU3kq4V4w/edit?resourcekey=0-pPo85Svkb9lKdtlrhMCTbw", "type": 301 }, @@ -534,6 +539,7 @@ { "source": "/go/icu-message-parser", "destination": "https://docs.google.com/document/d/1wj5rBbuz1bXM1lvt0s0d35F5JRtIPmYUs1UDk1G3HLc/edit?usp=sharing", "type": 301 }, { "source": "/go/idle-gc-metrics", "destination": "https://docs.google.com/document/d/1QjLTDr2cTsmS3DGIWdw9WSgqBMKJkFXD7Q8acziGIIA/edit?usp=sharing", "type": 301 }, { "source": "/go/image-decoding-registry", "destination": "https://docs.google.com/document/d/167qWrlaSAmJm5muqQ-iiJKZy6Q7ZlugIeXmraXava5Y/edit?resourcekey=0-mVrr_zBiSI2Qmd6vqae7Bw", "type": 301 }, + { "source": "/go/impeller-backend-desktop", "destination": "https://docs.google.com/document/d/14ou6WEfFjyFwtYZ81oCfIVdUYeG3vqIjvZ16eD0dNPw", "type": 301 }, { "source": "/go/impeller-dart", "destination": "https://docs.google.com/document/d/1Sh1BAC5c_kkuMVreo7ymBzPoMzb7lamZRPsI7GBXv5M/edit?resourcekey=0-5w8u2V-LS41tCHeoE8bDTQ", "type": 301 }, { "source": "/go/impeller-geometry", "destination": "https://docs.google.com/document/d/1t-dzzi04nCdkIReoJp8ZyGNskqIip1TG3WhGEz7Osd8/edit?usp=sharing", "type": 301 }, { "source": "/go/implicit-animations", "destination": "https://docs.google.com/document/d/1-gUqKiHf6w_eck1rZqYjQk0MMPOIlYjZtYu1zjsDLPA/edit?usp=sharing", "type": 301 }, @@ -621,6 +627,7 @@ { "source": "/go/null-safety-workshop", "destination": "https://dartpad.cn/workshops.html?webserver=https://dartpad-workshops-io2021.web.app/null_safety_workshop", "type": 301 }, { "source": "/go/nullable-cupertinothemedata-brightness", "destination": "https://docs.google.com/document/d/1qivq0CdkWGst5LU5iTLFUe_LTfLY84679-NxWiDgJXg/edit", "type": 301 }, { "source": "/go/ondirtycallbacks", "destination": "https://docs.google.com/document/d/1Vk_QWC92fFGxx2oIrIjkCL0ZZFHxmrprlLedyQsnkus/edit?usp=sharing", "type": 301 }, + { "source": "/go/onmaploaded", "destination": "https://docs.google.com/document/d/1ogwmAQOjbX6UoHnsQ1sQG2P0LRU2KRBED64jnjtBpIQ/edit?usp=sharing", "type": 301 }, { "source": "/go/opengl-on-ios", "destination":"https://docs.google.com/document/d/1kvrb6HeTRN4noAKO82o6x-Ii61PW-BQ-8s9YNtLgosM", "type": 301 }, { "source": "/go/optimized-platform-view-layers", "destination": "https://docs.google.com/document/d/1YHwVz7-F03psEzHxByGka_lIFaDV5K90BJMbNxQeK4k/edit?resourcekey=0-n4_VcUnMU-99sjJ6C1_Ycw#", "type": 301 }, { "source": "/go/os-adaptive-shortcut-activator", "destination": "https://docs.google.com/document/d/11NWj13MSDw1XQg4MpYIeqzcCFGuzQ_Cz7tYXW7fqzPY/edit?usp=sharing", "type": 301 }, @@ -660,6 +667,7 @@ { "source": "/go/remove-fab-accent-theme-dependency", "destination": "https://docs.google.com/document/d/1kmXWerkykXXjMfibsc9O105NocBGH3dGW8UL2VH5Q48/edit", "type": 301 }, { "source": "/go/remove-include-flutter-groovy", "destination": "https://docs.google.com/document/d/1OulURfRSWgJnnFA_cSup4ev7wrJwnZ7Siao_FGbLN9I", "type": 301 }, { "source": "/go/reorderable-list-view-update", "destination": "https://docs.google.com/document/d/1JzNtMQ-jnPnSHEBoi6IO0x2qMMylwhvRslEDcmqiwSs", "type": 301 }, + { "source": "/go/replace-block-semantics", "destination": "https://docs.google.com/document/d/1niJnShCR3-PIMYo1a9lWdVySBqr_wmXYPvBXntvcc0c/edit", "type": 301 }, { "source": "/go/respecting-tabcontroller-animateto-duration", "destination": "https://docs.google.com/document/d/1wzSzUYstDM-Kg5kwskyXcEUX7QTb-lBOso7Z5ebrCTk/edit", "type": 301 }, { "source": "/go/restoring-anonymous-routes", "destination": "https://docs.google.com/document/d/1vnjDruoiDz0eEd4eVzg3IS6W_LRaFxNSlKWtU6iXp2M/edit", "type": 301 }, { "source": "/go/rfc-32-bit-ios-support", "destination": "https://docs.google.com/document/d/1Xz8B2w42ZGmrSdvQHo6LVLhiSZBIxnHxZxoZ-8oyRUc/edit", "type": 301 }, @@ -703,6 +711,7 @@ { "source": "/go/swift-package-manager-plugins-v3", "destination": "https://docs.google.com/document/d/1aoa-t6zZBc6TosCa8N41w8aA8O4Z5yqdV4DvSRjWC54/edit?usp=sharing", "type": 301 }, { "source": "/go/synchronized-widgettester", "destination": "https://docs.google.com/document/d/1VumsuG6dEFUVpPQLqqKJnhI0CoIS9fCAMN-NFHIPmo0/edit", "type": 301 }, { "source": "/go/system-mouse-cursor", "destination": "https://docs.google.com/document/d/1bJLRy6flZ0wDCbpl2QA8SURUWXIvRJKMRRemxlOo1cA/edit", "type": 301 }, + { "source": "/go/system-ui", "destination": "https://docs.google.com/document/d/1JwQ7gdJ1evQ-IfDu7JjouwO8JXYC6fIw3TJzAYNWPPo/edit", "type": 301 }, { "source": "/go/table-cell-span", "destination": "https://docs.google.com/document/d/1OXTnWhI1bjZEfUsE3Cy8tm32ewMIwqKLXBKZPgTwGTM/edit?usp=sharing", "type": 301 }, { "source": "/go/table-development", "destination": "https://docs.google.com/document/d/1fCE-zQNql0nnqJXhycpg902lhL7jrOOz-6sT8s8tv5c/edit?usp=sharing", "type": 301 }, { "source": "/go/table-view", "destination": "https://docs.google.com/document/d/15ecTZE1g3WeswLGFWrnEgMP6SyL6jDRdxOgPsczOcV0/edit?usp=sharing&resourcekey=0-yNd_qFhiPjz6z2TgezWc0A", "type": 301 }, @@ -750,6 +759,7 @@ { "source": "/go/vector-graphics", "destination": "https://docs.google.com/document/d/1YWffrlc6ZqRwfIiR1qwp1AOkS9JyA_lEURI8p5PsZlg/edit", "type": 301 }, { "source": "/go/verbatim-logical-keys-for-non-latin-layout", "destination": "https://docs.google.com/document/d/1ZNgNxito-NVUS1JKlcjdzHUcKyyiupy8Za-jb131dD4/edit?usp=sharing&resourcekey=0-eeEZ5X0jAzuqSTdmPWlv0w", "type": 301 }, { "source": "/go/video-player-background-playback", "destination": "https://docs.google.com/document/d/12WgZg2qzpCGyCRgZbG4Jr8msAV8Jd4_MTzqWimhg56M/edit?usp=sharing", "type": 301 }, + { "source": "/go/video-player-drm", "destination": "https://docs.google.com/document/d/1T4LRRsicr_JuSkXBAXEcv7-9BnYPhmwgYnr1d6G6_o8/edit?usp=sharing", "type": 301 }, { "source": "/go/web-add-to-app", "destination": "https://docs.google.com/document/d/1rFM0qmYds1JkntTruBh8eO72xbT2KoP9JdbizucAAwQ/edit", "type": 301 }, { "source": "/go/web-astral-projections", "destination": "https://docs.google.com/document/d/1pvH-J8opXsjntTpFf1bbsbU8N1vFd8tPKvBgcNn8UKQ", "type": 301 }, { "source": "/go/web-cleanup-service-worker", "destination": "https://docs.google.com/document/d/1czOm3Hmy_oIq3NJStezb9AjwkKyta3NospkCy_DDv9E/edit", "type": 301 }, diff --git a/packages/analysis_defaults/analysis_options.yaml b/packages/analysis_defaults/analysis_options.yaml new file mode 100644 index 0000000000..b9bdf805ac --- /dev/null +++ b/packages/analysis_defaults/analysis_options.yaml @@ -0,0 +1 @@ +include: package:analysis_defaults/analysis.yaml diff --git a/packages/analysis_defaults/lib/analysis.yaml b/packages/analysis_defaults/lib/analysis.yaml new file mode 100644 index 0000000000..1124c9938b --- /dev/null +++ b/packages/analysis_defaults/lib/analysis.yaml @@ -0,0 +1,26 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + +linter: + rules: + - deprecated_member_use_from_same_package + - discarded_futures + - eol_at_end_of_file + - implicit_reopen + - invalid_case_patterns + - matching_super_parameters + - missing_code_block_language_in_doc_comment + - no_literal_bool_comparisons + - no_self_assignments + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - unnecessary_breaks + - unnecessary_null_aware_operator_on_extension_on_nullable + - use_enums + - use_truncating_division diff --git a/packages/analysis_defaults/pubspec.yaml b/packages/analysis_defaults/pubspec.yaml new file mode 100644 index 0000000000..bbf8f5f2e2 --- /dev/null +++ b/packages/analysis_defaults/pubspec.yaml @@ -0,0 +1,12 @@ +name: analysis_defaults +description: Analysis defaults for Dart/Flutter site tools. +publish_to: none + +resolution: workspace +environment: + sdk: ^3.11.0 + +# NOTE: Code isn't allowed in this package. +# Don't add dependencies besides the underlying lints package. +dependencies: + dart_flutter_team_lints: ^3.5.2 diff --git a/packages/excerpter/LICENSE b/packages/excerpter/LICENSE new file mode 100644 index 0000000000..c1503e154c --- /dev/null +++ b/packages/excerpter/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2017 Dart Project Authors +Copyright (c) 2026 The Flutter Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/excerpter/README.md b/packages/excerpter/README.md new file mode 100644 index 0000000000..99082198e9 --- /dev/null +++ b/packages/excerpter/README.md @@ -0,0 +1,235 @@ +# Markdown code-block excerpt updater + +Tooling to update code excerpts in Markdown documentation +from regions declared in source files elsewhere. + +> [!WARNING] +> This package is still a work-in-progress and not completely functional. + +## Install + +This package is not currently published on pub, +so you must use it as a [git][git-dep] or [path][path-dep] dependency. + +[git-dep]: https://dart.dev/tools/pub/dependencies#git-packages +[path-dep]: https://dart.dev/tools/pub/dependencies#path-packages + +## Defining excerpt regions + +The package pulls content from Dart files that are +optionally split up with code regions. + +To include lines within a code region, +add a Dart line comment before the first line in the region with +`#docregion ` where `` is your desired name for the region. +Then after the final line of the region, +add a Dart line comment with `#enddocregion ` with the same name. +You can open and close a docregion multiple times within a file. + +Region names must be a non-empty sequence of alphanumeric characters, +optionally containing dashes (`-`). + +A single docregion comment can open or close multiple regions +with a comma-delimited list. + +### Annotated example + +The following Dart snippet is an example of using +docregion comments in a few different ways. + +```dart +// #docregion imports +import 'dart:async'; +// #enddocregion imports + +// #docregion main, main-stub +void main() async { + // #enddocregion main-stub + print('Compute π using the Monte Carlo method.'); + await for (final estimate in computePi().take(500)) { + print('π ≅ $estimate'); + } + // #docregion main-stub +} +// #enddocregion main, main-stub + +/// Generates a stream of increasingly accurate estimates of π. +Stream computePi({int batch = 100000}) async* { + // ... +} +``` + +The regions defined in this Dart snippet are: +`imports`, `main`, `main-stub`, and the unnamed region for the entire file. + +Some of the regions defined in the example above include: + +- `imports` region: + + ```dart + import 'dart:async'; + ``` + +- `main-stub` region: + + ```dart + void main() async { + // ··· + } + ``` + +The `main-stub` region is discontinuous as it has a break in it. +When this package is run to update excerpts, each break is +replaced by a language-specific comment filled with a plaster marker (`...`). + +## Injecting excerpts + +To inject content from docregions or entire files into Markdown files, +you use a special syntax to inject into a code block +or configure the injecting logic. + +In both cases, the syntax starts with `\`. + +### Inject instruction + +Use an inject instruction to inject a docregion from a source file +into the current Markdown file, with optional arguments +to configure the injection. + +Inject instructions must precede a Markdown code block +that is denoted with the language in the target file. + +````md + +```dart +void main() {} +``` +```` + +The first unnamed argument, surrounded in double quotes, +is the path to source file to pull regions from, +relative to the base source path set by the CLI. +A specific region from the file can be specified in parentheses, +otherwise the entire file is extracted. + +The following table outlines the parameters supported by injection instructions. +Note that specified arguments, such as transformations, are applied +in the order they appear in the instruction. + +| Parameter | Argument | Description | +|-------------|------------------------|-------------------------------------------------------------------------------| +| `indent-by` | `int` | The amount to indent each line by. | +| `plaster` | `String` | The plaster template to use, or `none` to disable. | +| `skip` | `int` | The amount of lines to skip at the beginning if positive, or end if negative. | +| `take` | `int` | The amount of lines to take at the beginning if positive, or end if negative. | +| `remove` | `String\|RegExp` | Remove the lines containing the specified pattern. | +| `retain` | `String\|RegExp` | Keep the lines containing the specified pattern. | +| `from` | `String\|RegExp` | Keep the lines after and including the first one with the specified pattern. | +| `to` | `String\|RegExp` | Keep the lines before and including the first one with the specified pattern. | +| `replace` | [Replacement syntax][] | Replace text with the specified pattern to the specified string. | + +[Replacement syntax]: #replacement-syntax + +For parameters that accept a `RegExp`, +they follow the Dart VM's supported syntax, +and must be wrapped in forward slashes, such as `//`. +If you're passing a normal string, the forward slashes are unnecessary. + +### Replacement syntax + +The `replace` argument accepts one or more semicolon separated +regular expression and replacement expression pairs. +The replacement expressions can be simple strings or +include backreferences to numbered capture groups from the +regular expression using, `$&`, `$1`, `$2`, and so on. + +The following replace expression replaces text like `Hello world` +with `[!Hello!] world`: + +```md +replace="/(Hello)( world)/[!$1!]$2/g;" +``` + +Compared to other transforms, replace transforms are +applied on the entire excerpt rather than per line. + +### Set instruction + +Use a set instruction to configure one of the following for +subsequent injection instructions: + +- The base directory that source files for docregions are found in. +- The template to be used for plaster lines. +- Replace expressions that will run for every inject instruction. + +Only one set instruction argument can be used at a time, +and only one of each can exist in the file. +Subsequent set instructions of the same type override the previous ones. + +#### Modify the source file base path + +To set the base directory that source files for docregions are found in +to a subdirectory of the CLI provided one, use the `path-base` argument: + +```md + +``` + +#### Modify the plaster template + +To change the template used for plaster lines, use the `plaster` argument: + +```md + +``` + +If you want to use the default plaster content specified by the CLI, +you can use `$defaultPlaster` within the template: + +```md + +``` + +#### Add a global transform + +To add a transform expression that is applied to all subsequent excerpts +use the `replace` argument and the same [replacement syntax][] as above. + +```md + +``` + +[replacement syntax]: #replacement-syntax + +#### Reset set instructions + +To reset any of the instructions, +use the same arguments set to an empty string (`""`); + +## Updating excerpts + +To update the excerpts specified by injection instructions +within your Markdown files, you can either use the package +as a library through the `Updater` class, or the CLI. + +```bash +dart run excerpter [OPTIONS] +``` + +| Option | Description | +|---------------------|---------------------------------------------------------------------------------------------------------------------------------| +| `--dry-run ` | If the updater should only report if excerpts need to be updated. | +| `--fail-on-update` | Report a non-zero exit code if an excerpt is or needs to be updated. | +| `--exclude` | Regular expressions of paths to exclude when processing a directory recursively. Dot files and directories are always excluded. | +| `--base-source` | The path to the directory containing the source files that excerpt regions should be retrieved from. | +| `--plaster-content` | The default plaster content, such as "..." or "···". | +| `--replace` | A replacement to run on every excerpt. Refer to the [replacement syntax](#replacement-syntax) for more details. | + +## Learn more + +To learn more about the tool, check out +the various usages across the [dart.dev][] and [docs.flutter.dev][] +website repositories. + +[dart.dev]: https://github.com/dart-lang/site-www +[docs.flutter.dev]: https://github.com/flutter/website diff --git a/packages/excerpter/analysis_options.yaml b/packages/excerpter/analysis_options.yaml new file mode 100644 index 0000000000..b29fbd0aee --- /dev/null +++ b/packages/excerpter/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:analysis_defaults/analysis.yaml + +analyzer: + exclude: + - test_data/ diff --git a/packages/excerpter/bin/excerpter.dart b/packages/excerpter/bin/excerpter.dart new file mode 100644 index 0000000000..d64018a837 --- /dev/null +++ b/packages/excerpter/bin/excerpter.dart @@ -0,0 +1,141 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +import 'dart:io' as io; + +import 'package:args/args.dart'; +import 'package:excerpter/excerpter.dart'; +import 'package:path/path.dart' as path; + +void main(final List args) async { + await runExcerpter(args); +} + +/// Run the code excerpter with the specified [arguments], +/// usually meant to be called from a command-line app. +Future runExcerpter(final List arguments) async { + final ArgResults results; + try { + results = _argParser.parse(arguments); + } on FormatException catch (e) { + _printUsageAndExit(message: e.message); + } + + final dryRun = results[_dryRunFlag] as bool? ?? false; + final failOnUpdate = results[_failOnUpdateFlag] as bool? ?? false; + final excludePaths = results[_excludeOption] as List? ?? const []; + final plasterContent = results[_plasterContentOption] as String? ?? '...'; + final replaceInstructions = results[_replaceOption] as String?; + final baseSourcePath = path.absolute( + results[_baseSourcePathOption] as String? ?? path.current, + ); + + if (results.rest.length != 1) { + _printUsageAndExit( + message: 'You must specify a file or directory to run updates on.', + ); + } + final updatePath = path.absolute(results.rest.first); + + final replaceTransforms = replaceInstructions == null + ? const [] + : stringToReplaceTransforms( + replaceInstructions, + (e) => _printUsageAndExit(message: e), + ); + + final updater = Updater( + baseSourcePath: baseSourcePath, + validTargetExtensions: const {'.md'}, + defaultPlasterContent: plasterContent, + defaultTransforms: replaceTransforms, + excludePaths: excludePaths.map(RegExp.new), + ); + + final result = await updater.update(updatePath, makeUpdates: !dryRun); + final warnings = result.warnings; + final warningCount = warnings.length; + final errors = result.errors; + final errorCount = errors.length; + if (warningCount > 0 || errorCount > 0) { + for (final error in errors) { + print(' error - $error'); + } + for (final warning in warnings) { + print('warning - $warning'); + } + print(''); + print('$errorCount errors and $warningCount warnings found!'); + print(''); + } + + print( + 'Processed ${result.filesVisited} out of ' + '${result.totalFilesToVisit} files: ' + '${result.excerptsNeedingUpdates} out of ' + '${result.excerptsVisited} excerpts visited ' + '${result.madeUpdates ? 'were updated' : 'need to be updated'}.', + ); + + if (result.errors.length case final amountOfErrors when amountOfErrors > 0) { + io.exitCode = amountOfErrors; + } else if (failOnUpdate && result.excerptsNeedingUpdates > 0) { + io.exitCode = result.excerptsNeedingUpdates; + } +} + +final _argParser = ArgParser() + ..addFlag( + _dryRunFlag, + negatable: false, + help: 'If the updater should only report if excerpts need to be updated.', + ) + ..addFlag( + _failOnUpdateFlag, + negatable: false, + help: + 'Report a non-zero exit code if ' + 'an excerpt is or needs to be updated.', + ) + ..addMultiOption( + _excludeOption, + help: + 'Regular expressions of paths to exclude when ' + 'processing a directory recursively.\n' + 'Dot files and directories are always excluded.', + ) + ..addOption( + _baseSourcePathOption, + help: + 'The path to the directory containing the source files that ' + 'excerpt regions should be retrieved from.', + ) + ..addOption( + _plasterContentOption, + help: 'The default plaster content, such as "..." or "···".', + ) + ..addOption( + _replaceOption, + help: + 'A replacement to run on every excerpt.\n' + 'Refer to the package docs for syntax help.', + ); + +/// Print the usage information for this command, +/// optionally with the specified error [message] and [exitCode], +/// then exit. +/// +/// If no [exitCode] is specified, exit with a code of `1`, indicating failure. +Never _printUsageAndExit({String? message, int exitCode = 1}) { + if (message != null) print('\n$message\n'); + print('Usage: excerpter [OPTIONS] file_or_directory\n'); + print(_argParser.usage); + io.exit(exitCode); +} + +const String _dryRunFlag = 'dry-run'; +const String _failOnUpdateFlag = 'fail-on-update'; +const String _excludeOption = 'exclude'; +const String _plasterContentOption = 'plaster-content'; +const String _replaceOption = 'replace'; +const String _baseSourcePathOption = 'base-source'; diff --git a/packages/excerpter/lib/excerpter.dart b/packages/excerpter/lib/excerpter.dart new file mode 100644 index 0000000000..48fce4eb16 --- /dev/null +++ b/packages/excerpter/lib/excerpter.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +/// Tooling to update code excerpts in Markdown documentation +/// from regions declared in source files elsewhere. +library; + +export 'src/transform.dart'; +export 'src/update.dart'; diff --git a/packages/excerpter/lib/src/extract.dart b/packages/excerpter/lib/src/extract.dart new file mode 100644 index 0000000000..1577a0133e --- /dev/null +++ b/packages/excerpter/lib/src/extract.dart @@ -0,0 +1,215 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +import 'dart:io'; +import 'dart:math' as math show min; + +import 'package:meta/meta.dart'; + +/// A tool to extract declared docregions from specified source files. +/// +/// Caches all docregions parsed from files, so consider +/// creating a new one for each file you're injecting in to. +final class ExcerptExtractor { + /// A cache of file paths to to the regions found within them. + /// + /// If the value a pile path points to is `null`, + /// the file couldn't be found or read. + final Map?> _regionCacheByPath = {}; + + /// Extract the region with the specified [regionName] from + /// the file located at the specified [path]. + /// + /// If a file does not exist at that location or the [regionName] + /// does not exist either, a `ExtractException` will be thrown. + @useResult + Future extractRegion(String path, String regionName) async { + if (!_regionCacheByPath.containsKey(path)) { + _regionCacheByPath[path] = await _extractAllRegions(path); + } + final regions = _regionCacheByPath[path]; + if (regions == null) { + throw ExtractException('No file exists at $path.'); + } + + final region = regions[regionName]; + if (region == null) { + throw ExtractException( + 'The region "$regionName" does not exist in the file at $path.', + ); + } + + return region; + } + + @useResult + Future?> _extractAllRegions(String path) async { + final file = File(path); + if (!(await file.exists())) { + return null; + } + + final lines = await file.readAsLines(); + if (lines.isEmpty) { + return const {}; + } + + final regionContent = {_entireFileRegionName: Region._()}; + final currentRegions = {_entireFileRegionName}; + + for (var lineIndex = 0; lineIndex < lines.length; lineIndex += 1) { + final line = lines[lineIndex]; + final trimmedLine = line.trimLeft(); + final indent = line.length - trimmedLine.length; + + final directive = _docRegionDirective.firstMatch(trimmedLine); + if (directive != null) { + final isEnd = directive.namedGroup('end') != null; + final rawRegionNames = directive.namedGroup('regions'); + if (rawRegionNames == null) { + throw const ExtractException( + 'A docregion comment must specify at least one region!', + ); + } + final regionNames = rawRegionNames.split(','); + for (final rawRegionName in regionNames) { + final regionName = rawRegionName.trim(); + if (regionName.isEmpty) { + throw const ExtractException( + 'docregion comment tried to use an empty region name.', + ); + } + if (isEnd) { + final removed = currentRegions.remove(regionName); + if (!removed) { + throw ExtractException( + 'enddocregion tried to close the ' + "unopened '$regionName' region!", + ); + } + } else { + if (regionContent[regionName] case final region?) { + // If the region already exists, add a plaster line. + region._addPlaster(indent); + } else { + regionContent[regionName] = Region._(); + } + + currentRegions.add(regionName); + } + } + } else { + // Just a normal line. + for (final region in currentRegions) { + regionContent[region]!._addLine(line, indent); + } + } + } + + currentRegions.remove(_entireFileRegionName); + if (currentRegions.isNotEmpty) { + throw ExtractException('Regions $currentRegions were not closed.'); + } + + return regionContent; + } +} + +/// The contents of a docregion found in a file. +final class Region { + /// The untransformed text lines or plaster lines of the docregion. + final List<_RegionLine> _lines = []; + + /// The minimum indent seen in this region. + /// + /// `99999` is the initial value as no line should be longer than that... + int _minIndent = 99999; + + /// Creates a [Region] with the specified indentation, + /// usually from the docregion comment. + Region._(); + + /// Adds the specified [line] with the specified [indent] + /// to the contents of the region. + void _addLine(String line, int indent) { + _lines.add(_StringLine(line)); + + // Ignore the indent of blank lines. + if (line.trim().isNotEmpty) { + _minIndent = math.min(_minIndent, indent); + } + } + + /// Adds a line where a plaster could be inserted + /// as well as the [directiveIndent] of the directive adding it. + /// + /// This is usually when a docregion is closed and opened again. + void _addPlaster(int directiveIndent) { + _lines.add(_PlasterLine(directiveIndent)); + } + + /// Builds a list of strings from the region, + /// replacing lines marked as plasters with the + /// specified [plaster] content, + /// and applying the minimum indentation to each line. + /// + /// If [plaster] is `null` or `'none'`, + /// the plaster lines are not included at all. + @useResult + Iterable linesWithPlaster(final String? plaster) { + final updatedLines = []; + final includePlaster = plaster != null && plaster != 'none'; + + for (final line in _lines) { + switch (line) { + case _PlasterLine(:final directiveIndent): + if (includePlaster) { + final minimizedDirectiveIndent = directiveIndent - _minIndent; + updatedLines.add('${' ' * minimizedDirectiveIndent}$plaster'); + } + case _StringLine(:final line): + if (_minIndent == 0 || line.length < _minIndent) { + updatedLines.add(line); + } else { + updatedLines.add(line.substring(_minIndent)); + } + } + } + + return updatedLines; + } +} + +sealed class _RegionLine {} + +final class _StringLine extends _RegionLine { + final String line; + + _StringLine(this.line); +} + +final class _PlasterLine extends _RegionLine { + final int directiveIndent; + + _PlasterLine(this.directiveIndent); +} + +/// An exception thrown when a [ExcerptExtractor] +/// failed to extract a region. +@immutable +final class ExtractException implements Exception { + /// The error causing the exception during extraction. + final String error; + + /// Create a [ExtractException] with the specified [error]. + const ExtractException(this.error); + + @override + String toString() => error; +} + +const String _entireFileRegionName = ''; + +final RegExp _docRegionDirective = RegExp( + r'^.*?#(?end)?docregion\s(?[a-zA-Z0-9,_\-\s]+).*?$', +); diff --git a/packages/excerpter/lib/src/inject.dart b/packages/excerpter/lib/src/inject.dart new file mode 100644 index 0000000000..d3d643c4c2 --- /dev/null +++ b/packages/excerpter/lib/src/inject.dart @@ -0,0 +1,542 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; + +import 'extract.dart'; +import 'transform.dart'; + +/// An excerpt updater for an individual file that is +/// guaranteed to only run once. +final class FileUpdater { + /// The default transforms to run initially on all code excerpt regions. + final Iterable defaultTransforms; + + /// The path to the directory of the source files to pull regions from. + final String baseSourcePath; + + /// The content to include in plaster replacements by default. + final String defaultPlasterContent; + + /// The path to the file to update the excerpt regions of. + final String pathToUpdate; + + /// The update results for this file after + /// running [process] for the first time. + FileProcessResults? _results; + + /// Create a new [FileUpdater] that will process the file at the + /// specified [pathToUpdate], pull code excerpt regions from the + /// directory at [baseSourcePath], then optionally update + /// the file with updated regions. + /// + /// The [defaultPlasterContent] is used when applying plasters + /// if no the page or inject instruction doesn't override it. + /// + /// The [defaultTransforms] specified are ran to transform + /// each regions pulled from the source files. + FileUpdater( + this.pathToUpdate, { + required this.baseSourcePath, + required this.defaultPlasterContent, + required this.defaultTransforms, + }); + + /// Process the file at [pathToUpdate] and determine + /// what, if any, updates need to be made to its injected + /// code excerpts. + Future process() async { + if (_results case final results?) return results; + + final updatedContent = StringBuffer(); + final warnings = []; + + var excerptsVisited = 0; + final excerptsUpdated = <({int instructionLine, String updated})>[]; + + final extractor = ExcerptExtractor(); + + final originalLines = await File(pathToUpdate).readAsLines(); + + Iterable wholeFileTransforms = []; + String? wholeFilePlasterTemplate; + var wholeFilePathBase = ''; + + for (var lineIndex = 0; lineIndex < originalLines.length; lineIndex += 1) { + final line = originalLines[lineIndex]; + final trimmedLine = line.trimLeft(); + + // The line won't change whether if + // it's an instruction or not in a excerpt. + updatedContent.writeln(line); + if (!trimmedLine.startsWith(_instructionStart)) { + continue; + } + + final instructionLineNumber = lineIndex + 1; + final instructionIndent = line.length - trimmedLine.length; + + Never reportError(String error) { + throw InjectionException._( + pathToUpdate, + line, + instructionLineNumber, + error, + ); + } + + final instruction = _Instruction.fromLine(line, reportError); + if (instruction case _SetInstruction()) { + switch (instruction) { + case _SetPathBaseInstruction(:final pathBase): + wholeFilePathBase = pathBase; + case _SetPlasterInstruction(:final plasterTemplate): + wholeFilePlasterTemplate = plasterTemplate; + case _SetFileReplaceInstruction(:final transforms): + wholeFileTransforms = transforms; + } + } else if (instruction case _InjectInstruction()) { + // Move to index of what should be code block opening + lineIndex += 1; + + if (lineIndex >= originalLines.length) { + reportError( + 'An inject instruction must be followed by a code block. ' + 'Found end of file.', + ); + } + + final lineAfterInstruction = originalLines[lineIndex]; + final fencedCodeBlock = _codeBlockStart.firstMatch( + lineAfterInstruction, + ); + if (fencedCodeBlock == null) { + reportError( + 'An inject instruction must be followed by a code block ' + 'with a language specified.', + ); + } + + excerptsVisited += 1; + + final backticks = fencedCodeBlock.namedGroup('backticks')!; + final backtickCount = backticks.length; + final language = fencedCodeBlock.namedGroup('language')!; + final codeBlockEndMarker = RegExp('^\\s*`{$backtickCount}.*?\$'); + + final oldLines = []; + String? codeBlockClose; + + while (lineIndex < originalLines.length) { + lineIndex += 1; + final codeLine = originalLines[lineIndex]; + + if (codeBlockEndMarker.firstMatch(codeLine) != null) { + codeBlockClose = codeLine; + break; + } + + oldLines.add(codeLine); + } + + if (codeBlockClose == null) { + reportError('Unclosed or unmatched code block.'); + } + + final combinedPath = path.join( + baseSourcePath, + wholeFilePathBase, + instruction.targetPath, + ); + + final Region region; + try { + region = await extractor.extractRegion( + combinedPath, + instruction.regionName, + ); + } on ExtractException catch (e) { + reportError(e.error); + } + + var plaster = (instruction.plasterTemplate ?? wholeFilePlasterTemplate) + ?.replaceAll(r'$defaultPlaster', defaultPlasterContent); + + if (plaster == null) { + final languageComments = _contentTypeToCommentFormat(language); + if (languageComments case (:final prefix, :final suffix)) { + plaster = '$prefix$defaultPlasterContent$suffix'; + } + } + + var updatedLines = region.linesWithPlaster(plaster); + + final transforms = [ + ...instruction.transforms, + ...wholeFileTransforms, + ...defaultTransforms, + ]; + + for (final transform in transforms) { + updatedLines = transform.transform(updatedLines); + } + + updatedLines = updatedLines.map((line) => line.trimRight()); + + // Remove all shared whitespace on the left. + int? sharedLeftWhitespace; + for (final line in updatedLines) { + final leftWhitespace = line.length - line.trimLeft().length; + // If this line has less left whitespace than preceding lines, + // use its count as the shared left whitespace. + if (sharedLeftWhitespace == null || + leftWhitespace < sharedLeftWhitespace) { + sharedLeftWhitespace = leftWhitespace; + } + } + + if (sharedLeftWhitespace != null && sharedLeftWhitespace > 0) { + updatedLines = [ + for (final line in updatedLines) + line.substring(sharedLeftWhitespace), + ]; + } + + // Add back the indentation from the file and any from the instruction. + updatedLines = IndentTransform( + instructionIndent + (instruction.indentBy ?? 0), + ).transform(updatedLines); + + final updatedExcerpt = updatedLines.join('\n'); + if (!(const IterableEquality().equals( + oldLines, + updatedLines, + ))) { + excerptsUpdated.add(( + instructionLine: instructionLineNumber, + updated: updatedExcerpt, + )); + } + + updatedContent.writeln(lineAfterInstruction); + updatedContent.writeln(updatedExcerpt); + updatedContent.writeln(codeBlockClose); + } + } + + return FileProcessResults._( + pathToUpdate, + updatedContent.toString(), + excerptsVisited, + excerptsUpdated, + warnings, + ); + } +} + +/// The results of processing a file and its injection instructions. +@immutable +final class FileProcessResults { + /// The path of the file to process and update. + final String pathToUpdate; + + /// The content of the file after applying updates. + final String updatedContent; + + /// The amount of excerpts visited in the file. + final int excerptsVisited; + + /// The warnings experienced while processing the file. + final List warnings; + + /// All instances where an excerpt needs to be updated, + /// including the line number in the original file of the instruction + /// and the contents after updating. + final List<({int instructionLine, String updated})> excerptsUpdated; + + /// Create a [FileProcessResults] to communicate the results of + /// processing a file and its code excerpt instructions. + FileProcessResults._( + this.pathToUpdate, + this.updatedContent, + this.excerptsVisited, + this.excerptsUpdated, + this.warnings, + ); + + /// If any excerpts at [pathToUpdate] need updates. + bool get needsUpdates => excerptsUpdated.isNotEmpty; + + /// Write the excerpt updates determined as necessary + /// to the file at [pathToUpdate]. + Future writeUpdates() async { + await File(pathToUpdate).writeAsString(updatedContent); + } +} + +/// An exception that occurs when a code-excerpt injection fails. +@immutable +final class InjectionException implements Exception { + /// The path of the file this injection instruction exception occurred for. + final String filePath; + + /// The line of the injection instruction that had errors or caused errors. + final String line; + + /// The line number of the injection instruction that + /// had errors or caused errors. + /// + /// This is the line number before the updates, if any, were made. + final int lineNumber; + + /// The error that occurred due to the injection instruction. + final String error; + + /// Create an exception to convey that an injection instruction of [line] + /// at [lineNumber] in the file at [filePath] had an error + /// or caused an error. + InjectionException._(this.filePath, this.line, this.lineNumber, this.error); + + @override + String toString() => '$filePath:$lineNumber - $error'; +} + +final RegExp _instructionPattern = RegExp( + r'^\s*<\?code-excerpt\s+(?:"(?\S+)(?:\s\((?[^)]+)\))?\s*")?(?.*?)\?>$', +); + +final RegExp _instructionStart = RegExp(r'^<\?code-excerpt'); + +final RegExp _codeBlockStart = RegExp( + r'^\s*(?`{3,})(?\S+).*?$', +); + +final RegExp _splitArgs = RegExp( + r'(?[-\w]+)\s*(=\s*"(?.*?)"\s*)\s*', +); + +/// A code excerpt set or injection instruction +/// found in a file being processed. +sealed class _Instruction { + /// Parse and return an [_Instruction] from the specified [line]. + /// + /// The line is assumed to at least look like like an instruction. + /// + /// Errors are reported to the calling function + /// through the [reportError] callback. + static _Instruction fromLine( + String line, + Never Function(String error) reportError, + ) { + final match = _instructionPattern.firstMatch(line); + if (match == null) { + reportError('Invalid code excerpt syntax.'); + } + + final path = match.namedGroup('path'); + final argumentString = match.namedGroup('args')?.trim() ?? ''; + final argumentPairs = _splitArgs + .allMatches(argumentString) + .map((m) => (arg: m.namedGroup('arg')!, value: m.namedGroup('value')!)); + + if (path == null) { + if (argumentPairs.length != 1) { + reportError('A set instruction must have only one argument specified.'); + } + final argName = argumentPairs.first.arg; + final argValue = argumentPairs.first.value; + return switch (argName) { + 'path-base' => _SetPathBaseInstruction(argValue), + 'plaster' => _SetPlasterInstruction(argValue), + 'replace' => _SetFileReplaceInstruction( + stringToReplaceTransforms(argValue, reportError), + ), + _ => reportError( + 'A set instruction can only specify the ' + '`path-base`, `plaster`, and `replace` arguments.', + ), + }; + } + + return _InjectInstruction.fromArgs( + targetPath: path, + regionName: match.namedGroup('region') ?? '', + arguments: argumentPairs, + reportError: reportError, + ); + } +} + +sealed class _SetInstruction extends _Instruction {} + +final class _SetPathBaseInstruction extends _SetInstruction { + final String pathBase; + + _SetPathBaseInstruction(this.pathBase); +} + +final class _SetPlasterInstruction extends _SetInstruction { + final String plasterTemplate; + + _SetPlasterInstruction(this.plasterTemplate); +} + +final class _SetFileReplaceInstruction extends _SetInstruction { + final Iterable transforms; + + _SetFileReplaceInstruction(this.transforms); +} + +/// An excerpt instruction that indicates an injection of content +/// from a source file and specified region, +/// with optional configuration such as transforms applied. +final class _InjectInstruction extends _Instruction { + final String targetPath; + final String regionName; + + final List transforms; + + final int? indentBy; + final String? plasterTemplate; + + _InjectInstruction({ + required this.targetPath, + required this.regionName, + required this.transforms, + this.indentBy, + this.plasterTemplate, + }); + + factory _InjectInstruction.fromArgs({ + required String targetPath, + required String regionName, + required Iterable<({String arg, String value})> arguments, + required Never Function(String error) reportError, + }) { + String? indentByString; + String? plasterTemplate; + + final transforms = []; + + for (final (arg: argName, value: argValue) in arguments) { + switch (argName) { + case 'indent-by': + if (indentByString != null) { + reportError( + 'The `indent-by` argument can only be ' + 'specified once per instruction.', + ); + } + indentByString = argValue; + case 'plaster': + if (plasterTemplate != null) { + reportError( + 'The `plaster` argument can only be ' + 'specified once per instruction.', + ); + } + plasterTemplate = argValue; + case 'skip': + transforms.add(SkipTransform(int.parse(argValue))); + case 'take': + transforms.add(TakeTransform(int.parse(argValue))); + case 'from': + transforms.add(FromTransform(_argStringToPattern(argValue))); + case 'to': + transforms.add(ToTransform(_argStringToPattern(argValue))); + case 'remove': + transforms.add(RemoveTransform(_argStringToPattern(argValue))); + case 'retain': + transforms.add(RetainTransform(_argStringToPattern(argValue))); + case 'replace': + transforms.addAll(stringToReplaceTransforms(argValue, reportError)); + default: + reportError( + '$argName is an unsupported argument ' + 'in inject instructions.', + ); + } + } + + final indentBy = indentByString == null ? null : int.parse(indentByString); + + if (indentBy != null && indentBy < 0) { + reportError('The `indent-by` argument must be positive.'); + } + + return _InjectInstruction( + targetPath: targetPath, + regionName: regionName, + indentBy: indentBy, + plasterTemplate: plasterTemplate, + transforms: transforms, + ); + } +} + +/// Convert the specified string [value] to a regular expression +/// if wrapped in `/`. +Pattern _argStringToPattern(String value) { + if (value.startsWith('/') && value.endsWith('/')) { + final regularExpression = value.substring(1, value.length - 1); + return RegExp(regularExpression); + } + + // Unescape an escaped starting slash. + if (value.startsWith(r'\/')) { + return value.substring(1); + } + + return value; +} + +/// Convert from the specified [contentType], +/// representing a content type names or programming language, to +/// its line comment format, including prefix and suffix. +/// +/// `null` is returned if no line comment format is known. +({String prefix, String suffix})? _contentTypeToCommentFormat( + String contentType, +) { + switch (contentType.trim().toLowerCase()) { + case 'c': + case 'c++': + case 'cc': + case 'cpp': + case 'cs': + case 'csharp': + case 'dart': + case 'go': + case 'gradle': + case 'groovy': + case 'java': + case 'javascript': + case 'js': + case 'kotlin': + case 'kt': + case 'objc': + case 'rs': + case 'sass': + case 'scss': + case 'swift': + case 'ts': + case 'typescript': + return (prefix: '// ', suffix: ''); + case 'css': + return (prefix: '/* ', suffix: ' */'); + case 'html': + case 'xml': + return (prefix: ''); + case 'python': + case 'py': + case 'yml': + case 'yaml': + return (prefix: '# ', suffix: ''); + case _: + return null; + } +} diff --git a/packages/excerpter/lib/src/transform.dart b/packages/excerpter/lib/src/transform.dart new file mode 100644 index 0000000000..a72e2014fe --- /dev/null +++ b/packages/excerpter/lib/src/transform.dart @@ -0,0 +1,320 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; + +/// A stored transformation that can transform from +/// a collection of strings, usually lines, +/// to a transformed version using [transform]. +sealed class Transform { + /// Transform the provided [lines] with the transform + /// defined by this type. + @useResult + Iterable transform(Iterable lines); +} + +/// A base class for transforms based off a singular [Pattern]. +abstract final class PatternTransform extends Transform { + /// The pattern to apply during the transform. + final Pattern pattern; + + /// The super constructor for transforms that apply a singular pattern. + PatternTransform(this.pattern); +} + +/// A transform to remove the lines that don't contain the specified [pattern]. +final class RetainTransform extends PatternTransform { + /// Create a [RetainTransform] that removes the lines that + /// don't contain the specified [pattern]. + RetainTransform(super.pattern); + + @override + Iterable transform(Iterable lines) { + return lines.where((line) => line.contains(pattern)); + } +} + +/// A transform to remove the lines that contain the specified [pattern]. +final class RemoveTransform extends PatternTransform { + /// Create a [RemoveTransform] that removes the lines that + /// do contain the specified [pattern]. + RemoveTransform(super.pattern); + + @override + Iterable transform(Iterable lines) { + return lines.whereNot((line) => line.contains(pattern)); + } +} + +/// A transform to keep the lines after and including the first one +/// that contains the specified [pattern]. +final class FromTransform extends PatternTransform { + /// Create a [FromTransform] that keeps the lines after and including + /// the first one that contains the specified [pattern]. + FromTransform(super.pattern); + + @override + Iterable transform(Iterable lines) { + return lines.skipWhile((line) => !line.contains(pattern)); + } +} + +/// A transform to keep only the lines before and including the first one +/// that contains the specified [pattern]. +final class ToTransform extends PatternTransform { + /// Create a [ToTransform] that keeps the lines before and including + /// the first one that contains the specified [pattern]. + ToTransform(super.pattern); + + @override + Iterable transform(Iterable lines) { + final newLines = []; + for (final line in lines) { + newLines.add(line); + if (line.contains(pattern)) { + break; + } + } + return newLines; + } +} + +/// A base class for transforms based off a singular integer [count]. +abstract final class AmountTransform extends Transform { + /// The count to use during the transform. + final int count; + + /// The super constructor for transforms that utilize a singular [count]. + AmountTransform(this.count); +} + +/// A transform to skip the first [count] lines if positive or +/// the last [count] lines if negative. +final class SkipTransform extends AmountTransform { + /// Create a [SkipTransform] that skips the first [count] lines if + /// positive or the last [count] lines if negative. + SkipTransform(super.count); + + @override + Iterable transform(Iterable lines) { + if (count.isNegative) { + return lines.take(lines.length + count); + } else { + return lines.skip(count); + } + } +} + +/// A transform to keep the first [count] lines if positive or +/// the last [count] lines if negative. +final class TakeTransform extends AmountTransform { + /// Create a [TakeTransform] that keeps the first [count] lines if + /// positive or the last [count] lines if negative. + TakeTransform(super.count); + + @override + Iterable transform(Iterable lines) { + if (count.isNegative) { + return lines.skip(lines.length + count); + } else { + return lines.take(count); + } + } +} + +/// A transform to indent each line with [count] whitespace. +final class IndentTransform extends AmountTransform { + /// Create a [IndentTransform] that indents each line with [count] whitespace. + IndentTransform(super.count) : assert(count >= 0); + + @override + Iterable transform(Iterable lines) { + if (count == 0) return lines; + + final indentString = ' ' * count; + return lines.map((line) => '$indentString$line'); + } +} + +/// Convert the [replaceInstructions] string that can include multiple +/// regular expression-based replacements to multiple +/// [ReplaceTransform] instances representing their transformation. +/// +/// Errors in parsing the [replaceInstructions] are reported to the +/// calling function through the [reportError] callback. +@useResult +Iterable stringToReplaceTransforms( + String replaceInstructions, + Never Function(String) reportError, +) { + final parts = replaceInstructions + .replaceAll(r'\/', _placeholderString) + .split('/') + .map((part) => part.replaceAll(_placeholderString, '/')) + .toList(growable: false); + + final length = parts.length; + if (length < 4 || length % 3 != 1) { + reportError('Each replace transform must have 3 parts.'); + } + + final start = parts[0]; + if (start.isNotEmpty) { + reportError('A replace transform must start with a forward slash (`/`).'); + } + + final transforms = []; + + for (var index = 1; index < length; index += 3) { + final end = parts[index + 2]; + if (!_endReplacePattern.hasMatch(end)) { + reportError('A replace transform must end with `g;`.'); + } + + final originalPattern = parts[index]; + final replaceWith = parts[index + 1]; + final encodedReplaceWith = _encodeSlashChar(replaceWith); + + if (!encodedReplaceWith.contains(_matchDollarNumRE)) { + transforms.add( + SimpleReplaceTransform( + RegExp(originalPattern, multiLine: true), + encodedReplaceWith, + ), + ); + } else { + transforms.add( + BackReferenceReplaceTransform( + RegExp(originalPattern, multiLine: true), + encodedReplaceWith, + ), + ); + } + } + + return transforms; +} + +/// A base class for replacement transforms that convert +/// text from one form to another, based on a provided [from] pattern +/// and [to] string. +sealed class ReplaceTransform extends Transform { + /// The pattern to match text to consider for replacement. + final Pattern from; + + /// The string to replace the text matching the [from] pattern with. + final String to; + + /// The super constructor for transforms that apply a replacement. + ReplaceTransform(this.from, this.to); + + /// Transform the provided [lines] with the transform + /// defined by this type. + /// + /// Note that, compared to other [Transform] types, + /// [ReplaceTransform] subtypes tend to operator on combined lines + /// rather than each line individually. + @override + Iterable transform(Iterable lines); +} + +/// A transform to replace each instance of the +/// [from] pattern with the [to] string. +final class SimpleReplaceTransform extends ReplaceTransform { + /// Create a [SimpleReplaceTransform] that replaces each instance of the + /// [from] pattern with the [to] string in the combined text. + SimpleReplaceTransform(super.from, super.to); + + @override + Iterable transform(Iterable lines) { + return lines.join('\n').replaceAll(from, to).split('\n'); + } +} + +/// A transform to replace each instance of the +/// [from] pattern with the [to] string while +/// allowing backreferences to captured groups in the [to] string. +final class BackReferenceReplaceTransform extends ReplaceTransform { + /// Create a [BackReferenceReplaceTransform] that replaces each instance of + /// the [from] pattern with the [to] string in the combined text, + /// while allowing backreferences to captured groups in the [to] string. + BackReferenceReplaceTransform(super.from, super.to); + + @override + Iterable transform(Iterable lines) { + return lines + .join('\n') + .replaceAllMapped( + from, + (match) => to.replaceAllMapped(_matchDollarNumRE, (replaceMatch) { + // The following works to match JS `replace` semantics. + // $$ becomes $ in a replacement string. + + final dollarSignCount = replaceMatch[1]!.length; + + // The escaped dollar sign characters present (if any). + final escapedDollarSigns = r'$' * (dollarSignCount ~/ 2); + + // Potentially a reference to a captured group, + // otherwise the content after the escaped dollar signs. + final potentialGroupReference = replaceMatch[2]; + + if (potentialGroupReference == null || + potentialGroupReference.isEmpty) { + return escapedDollarSigns; + } + + if (dollarSignCount.isEven) { + return '$escapedDollarSigns$potentialGroupReference'; + } + + // $& references the entire matched substring. + if (potentialGroupReference == '&') { + return '$escapedDollarSigns${match[0]}'; + } + + final groupNumber = int.tryParse(potentialGroupReference); + if (groupNumber == null || groupNumber > match.groupCount) { + // If there is no corresponding capture group, + // just output the reference itself. + return '$escapedDollarSigns\$$potentialGroupReference'; + } + + return '$escapedDollarSigns${match[groupNumber]}'; + }), + ) + .split('\n'); + } +} + +const String _placeholderString = '\u{0}'; + +final RegExp _matchDollarNumRE = RegExp(r'(\$+)(&|\d*)'); + +final RegExp _slashHexCharRE = RegExp(r'\\x(..)'); +final RegExp _slashLetterRE = RegExp(r'\\([\\nt])'); + +/// Encode special characters: '\t', `\n`, and `\xHH` where `HH` are hex digits. +String _encodeSlashChar(String s) => s + .replaceAllMapped(_slashLetterRE, (match) => _slashCharToChar(match[1])) + .replaceAllMapped( + _slashHexCharRE, + // At this point, escaped `\` is encoded as [_placeholderString]. + (match) => _hexToChar(match[1], errorValue: '\\x${match[1]}'), + ) + .replaceAll(_placeholderString, '\\'); // Recover `\` characters. + +String _hexToChar(String? hexDigits, {required String errorValue}) { + final charCode = int.tryParse(hexDigits ?? '', radix: 16); + return charCode == null ? errorValue : String.fromCharCode(charCode); +} + +String _slashCharToChar(String? char) => switch (char) { + 'n' => '\n', + 't' => '\t', + '\\' => _placeholderString, + _ => '\\$char', +}; + +final RegExp _endReplacePattern = RegExp(r'^g;?\s*$'); diff --git a/packages/excerpter/lib/src/update.dart b/packages/excerpter/lib/src/update.dart new file mode 100644 index 0000000000..8f8b342e49 --- /dev/null +++ b/packages/excerpter/lib/src/update.dart @@ -0,0 +1,190 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; + +import 'inject.dart'; +import 'transform.dart'; + +/// An excerpt updater that can run over the files with [validTargetExtensions] +/// in the specified paths with [update], and updates their code excerpts +/// according to their injection instructions. +final class Updater { + /// A pattern to check for paths to ignore or not descend in to. + static final RegExp _ignoreHiddenPathsPattern = RegExp(r'(^|/)\..*($|/)'); + + /// The transforms to run on each retrieved region. + final Iterable defaultTransforms; + + /// Patterns of paths to avoid updating. + final Iterable excludePaths; + + /// Extensions of files to consider for injection updating. + final Set validTargetExtensions; + + /// The path to the directory of the source files to pull regions from. + final String baseSourcePath; + + /// The content to include in plaster replacements by default. + final String defaultPlasterContent; + + /// Whether to continue updating or checking for updates if + /// an error is experienced. + final bool continueOnError; + + /// Create an [Updater] that can be used to [update] a collection of files + /// with the specified [validTargetExtensions] but + /// not matching [excludePaths]. + /// + /// Regions in injection instructions are pulled from files + /// within the [baseSourcePath]. + /// + /// If [withDefaultExclusions] is `true`, files and directories + /// hidden by starting with a `.` are excluded as well. + /// + /// The [defaultTransforms] specified are run on each region + /// before transforms provided by instructions on the page. + Updater({ + required this.baseSourcePath, + required this.validTargetExtensions, + this.continueOnError = false, + this.defaultPlasterContent = '...', + this.defaultTransforms = const [], + Iterable excludePaths = const [], + bool withDefaultExclusions = true, + }) : excludePaths = [ + if (withDefaultExclusions) _ignoreHiddenPathsPattern, + ...excludePaths, + ]; + + /// Use the configuration of this [Updater] to process the + /// file at the [pathToUpdate], optionally making updates + /// if [makeUpdates] is `true`. + @useResult + Future update( + String pathToUpdate, { + bool makeUpdates = true, + }) async { + final pathsToUpdate = await _findTargetFiles(pathToUpdate); + final warnings = []; + var excerptsVisited = 0; + var excerptsNeedingUpdates = 0; + var filesVisited = 0; + var madeUpdates = false; + final errors = []; + + for (final currentPath in pathsToUpdate) { + final fileUpdater = FileUpdater( + currentPath, + baseSourcePath: baseSourcePath, + defaultPlasterContent: defaultPlasterContent, + defaultTransforms: defaultTransforms, + ); + + try { + final results = await fileUpdater.process(); + warnings.addAll(results.warnings); + excerptsVisited += results.excerptsVisited; + excerptsNeedingUpdates += results.excerptsUpdated.length; + + if (makeUpdates && results.needsUpdates) { + madeUpdates = true; + await results.writeUpdates(); + } + + filesVisited += 1; + } on InjectionException catch (e) { + errors.add(e.toString()); + if (!continueOnError) { + break; + } + } + } + + return ExcerptUpdateResult._( + excerptsVisited: excerptsVisited, + excerptsNeedingUpdates: excerptsNeedingUpdates, + madeUpdates: madeUpdates, + filesVisited: filesVisited, + totalFilesToVisit: pathsToUpdate.length, + warnings: warnings, + errors: errors, + ); + } + + @useResult + Future> _findTargetFiles(String pathToEntity) async { + if (_shouldExclude(pathToEntity)) return const []; + + final entityType = await FileSystemEntity.type(pathToEntity); + if (entityType == FileSystemEntityType.directory) { + return [ + await for (final entity in Directory( + pathToEntity, + ).list(followLinks: false)) + ...await _findTargetFiles(entity.path), + ]; + } else if (entityType == FileSystemEntityType.file && + validTargetExtensions.contains(path.extension(pathToEntity))) { + return [pathToEntity]; + } + + return const []; + } + + bool _shouldExclude(String path) => + excludePaths.any((excludePattern) => path.contains(excludePattern)); +} + +/// The result of running a [Updater] on a collection of files. +@immutable +final class ExcerptUpdateResult { + /// The non-fatal warnings experienced when + /// processing the specified collection of files. + final Iterable warnings; + + /// The fatal errors experienced when + /// processing the specified collection of files. + final Iterable errors; + + /// The amount of code excerpt injections visited when + /// processing the specified collection of files. + /// + /// Note this may not be all excerpts in the collection + /// as a fatal error may have been experienced. + final int excerptsVisited; + + /// The amount of code excerpt injections that needed updates when + /// processing the specified collection of files. + /// + /// Note this may not be all excerpts needing updates in the collection + /// as a fatal error may have been experienced. + final int excerptsNeedingUpdates; + + /// The amount of files visited successfully without errors when + /// processing the specified collection of files. + /// + /// If this differs from [totalFilesToVisit], that means + /// there was a fatal error during processing. + final int filesVisited; + + /// The total amount of files in the specified collection of files + /// that were meant to be visited. + final int totalFilesToVisit; + + /// If the [Updater] wrote updates to the files needing them. + final bool madeUpdates; + + ExcerptUpdateResult._({ + required this.excerptsVisited, + required this.excerptsNeedingUpdates, + required this.filesVisited, + required this.totalFilesToVisit, + required this.madeUpdates, + this.warnings = const [], + this.errors = const [], + }); +} diff --git a/packages/excerpter/pubspec.yaml b/packages/excerpter/pubspec.yaml new file mode 100644 index 0000000000..9c4208deab --- /dev/null +++ b/packages/excerpter/pubspec.yaml @@ -0,0 +1,25 @@ +name: excerpter +description: Update code excerpts in Markdown documentation from source files. +version: 0.1.0 +publish_to: none + +resolution: workspace +environment: + sdk: ^3.11.0 + +dependencies: + args: ^2.6.0 + collection: ^1.19.0 + file: ^7.0.0 + glob: ^2.1.2 + meta: ^1.16.0 + path: ^1.9.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + io: ^1.0.5 + test: ^1.28.0 + +executables: + excerpter: excerpter diff --git a/packages/excerpter/test/cli_test.dart b/packages/excerpter/test/cli_test.dart new file mode 100644 index 0000000000..4a729e6fa7 --- /dev/null +++ b/packages/excerpter/test/cli_test.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +void main() { + final excerpterPath = path.join('bin', 'excerpter.dart'); + + test('no args', () { + final process = Process.runSync(Platform.executable, [ + 'run', + excerpterPath, + ]); + + expect(process.exitCode, equals(1)); + }); + + test('invalid format', () { + final process = Process.runSync(Platform.executable, [ + 'run', + excerpterPath, + '-f-23-423-4', + ]); + + expect(process.exitCode, equals(1)); + }); +} diff --git a/packages/excerpter/test/transform_test.dart b/packages/excerpter/test/transform_test.dart new file mode 100644 index 0000000000..dfdc50d74f --- /dev/null +++ b/packages/excerpter/test/transform_test.dart @@ -0,0 +1,242 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +import 'package:excerpter/excerpter.dart'; +import 'package:test/test.dart'; + +void main() { + group('Pattern transforms', _patternTransforms); + group('Amount transforms', _amountTransforms); + group('Replace transforms', _replaceTransforms); + group('String to replace transforms', _stringToReplaceTransforms); +} + +void _patternTransforms() { + test('retain all', () { + final all = ['aaa', 'aabb', 'abc', 'aacc']; + expect(RetainTransform('a').transform(all), orderedEquals(all)); + }); + + test('retain some', () { + expect( + RetainTransform('b').transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals(['aabb', 'abc']), + ); + }); + + test('retain none', () { + expect( + RetainTransform('d').transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals([]), + ); + }); + + test('remove all', () { + final all = ['aaa', 'aabb', 'abc', 'aacc']; + expect(RemoveTransform('a').transform(all), orderedEquals([])); + }); + + test('remove some', () { + expect( + RemoveTransform('b').transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals(['aaa', 'cccc']), + ); + }); + + test('remove none', () { + final all = ['aaa', 'aabb', 'abc', 'cccc']; + expect(RemoveTransform('d').transform(all), orderedEquals(all)); + }); + + test('from all', () { + final all = ['aaa', 'aabb', 'abc', 'aacc']; + expect(FromTransform('aaa').transform(all), orderedEquals(all)); + }); + + test('from some', () { + expect( + FromTransform('abc').transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals(['abc', 'cccc']), + ); + }); + + test('from none', () { + expect( + FromTransform('d').transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals([]), + ); + }); + + test('to all', () { + final all = ['aaa', 'aabb', 'abc', 'aacc']; + expect(ToTransform('aacc').transform(all), orderedEquals(all)); + }); + + test('to some', () { + expect( + ToTransform('aabb').transform(['aaa', 'aabb', 'abc', 'aacc']), + orderedEquals(['aaa', 'aabb']), + ); + }); + + test('to none', () { + final all = ['aaa', 'aabb', 'abc', 'cccc']; + expect(ToTransform('d').transform(all), orderedEquals(all)); + }); +} + +void _amountTransforms() { + test('skip negative', () { + expect( + SkipTransform(-2).transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals(['aaa', 'aabb']), + ); + }); + + test('skip zero', () { + final all = ['aaa', 'aabb', 'abc', 'cccc']; + expect(SkipTransform(0).transform(all), orderedEquals(all)); + }); + + test('skip positive', () { + expect( + SkipTransform(2).transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals(['abc', 'cccc']), + ); + }); + + test('skip all', () { + expect( + SkipTransform(4).transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals([]), + ); + }); + + test('take negative', () { + expect( + TakeTransform(-2).transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals(['abc', 'cccc']), + ); + }); + + test('take zero', () { + expect( + TakeTransform(0).transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals([]), + ); + }); + + test('take positive', () { + expect( + TakeTransform(2).transform(['aaa', 'aabb', 'abc', 'cccc']), + orderedEquals(['aaa', 'aabb']), + ); + }); + + test('take all', () { + final all = ['aaa', 'aabb', 'abc', 'cccc']; + expect(TakeTransform(4).transform(all), orderedEquals(all)); + }); + + test('indent negative', () { + expect(() => IndentTransform(-2), throwsA(isA())); + }); + + test('indent zero', () { + final all = ['a', ' b', ' c']; + expect(IndentTransform(0).transform(all), orderedEquals(all)); + }); + + test('indent positive', () { + expect( + IndentTransform(1).transform(['a', ' b', ' c']), + orderedEquals([' a', ' b', ' c']), + ); + }); +} + +void _replaceTransforms() { + test('replace simple some', () { + expect( + SimpleReplaceTransform( + RegExp('Hello'), + 'Halo', + ).transform(['Hello world!!', 'Bye!']), + orderedEquals(['Halo world!!', 'Bye!']), + ); + }); + + test('replace simple split', () { + expect( + SimpleReplaceTransform( + RegExp('Hi\nDash'), + 'Bye\nFriends', + ).transform(['Hi', 'Dash!']), + orderedEquals(['Bye', 'Friends!']), + ); + }); + + test('replace backreferences single capture group', () { + expect( + BackReferenceReplaceTransform( + RegExp('(Hello )Dash'), + r'$1World', + ).transform(['Hello Dash']), + orderedEquals(['Hello World']), + ); + }); + + test('replace backreferences entire captured', () { + expect( + BackReferenceReplaceTransform( + RegExp('Hello Dash'), + r'[!$&!]', + ).transform(['Hello Dash, you are very blue.']), + orderedEquals(['[!Hello Dash!], you are very blue.']), + ); + }); +} + +void _stringToReplaceTransforms() { + Never errorNotExpected(String error) { + fail('Error not expected - $error'); + } + + Never errorExpected(String error) { + throw _ExpectedException(); + } + + test('empty', () { + expect( + () => stringToReplaceTransforms('', errorExpected), + throwsA(isA<_ExpectedException>()), + ); + }); + + test('missing ending', () { + expect( + () => stringToReplaceTransforms('/Hello/Halo/', errorExpected), + throwsA(isA<_ExpectedException>()), + ); + }); + + test('single replace', () { + final simpleReplace = stringToReplaceTransforms( + '/Hello/Hi/g;', + errorNotExpected, + ); + expect(simpleReplace.length, equals(1)); + expect(simpleReplace.first.from, equals(RegExp('Hello', multiLine: true))); + expect(simpleReplace.first.to, equals('Hi')); + }); + + test('multiple replace', () { + final multipleReplace = stringToReplaceTransforms( + '/Hello/Hi/g;/World/Dash/g;', + errorNotExpected, + ); + expect(multipleReplace.length, equals(2)); + }); +} + +final class _ExpectedException implements Exception {} diff --git a/packages/excerpter/test/updater_test.dart b/packages/excerpter/test/updater_test.dart new file mode 100644 index 0000000000..9e15c21b99 --- /dev/null +++ b/packages/excerpter/test/updater_test.dart @@ -0,0 +1,77 @@ +// Copyright (c) 2023. All rights reserved. Use of this source code +// is governed by a MIT-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:excerpter/excerpter.dart'; +import 'package:io/io.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +void main() { + group('Updater setup', _setup); + group('Default update behavior', _defaultBehavior); +} + +void _setup() { + test('includes default exclusions', () { + expect( + Updater(baseSourcePath: 'example', validTargetExtensions: {'.md'}) + .excludePaths + .any((excludePattern) => excludePattern.hasMatch('.dart_tool')), + isTrue, + ); + }); +} + +void _defaultBehavior() { + final examplePath = path.join('test_data', 'example'); + final expectedPath = path.join('test_data', 'expected'); + final srcPath = path.join('test_data', 'src'); + final srcCopyPath = path.join('test_data', 'src_copy'); + + final updater = Updater( + baseSourcePath: examplePath, + validTargetExtensions: {'.md'}, + ); + + test('no updates - no errors - no update', () async { + final results = await updater.update(expectedPath, makeUpdates: false); + expect(results.warnings, hasLength(0)); + expect(results.errors, hasLength(0)); + expect(results.excerptsNeedingUpdates, equals(0)); + expect(results.excerptsVisited, greaterThan(0)); + expect(results.totalFilesToVisit, equals(4)); + expect(results.filesVisited, equals(4)); + expect(results.madeUpdates, isFalse); + }); + + test('update works', () async { + copyPathSync(srcPath, srcCopyPath); + + final results = await updater.update(srcCopyPath, makeUpdates: true); + expect(results.warnings, hasLength(0)); + expect(results.errors, hasLength(0)); + expect(results.excerptsNeedingUpdates, greaterThan(0)); + expect(results.excerptsNeedingUpdates, equals(results.excerptsVisited)); + expect(results.madeUpdates, isTrue); + + for (final expectedFile in Directory(expectedPath).listSync()) { + if (expectedFile is File) { + final updatedPath = path.join( + srcCopyPath, + path.basename(expectedFile.path), + ); + final updatedFile = File(updatedPath); + expect(updatedFile.existsSync(), isTrue); + + expect( + expectedFile.readAsStringSync(), + equals(updatedFile.readAsStringSync()), + ); + } + } + + Directory(srcCopyPath).deleteSync(recursive: true); + }); +} diff --git a/packages/excerpter/test_data/example/dartdoc.dart b/packages/excerpter/test_data/example/dartdoc.dart new file mode 100644 index 0000000000..9ef0625dbb --- /dev/null +++ b/packages/excerpter/test_data/example/dartdoc.dart @@ -0,0 +1,4 @@ +/// ```html +///

HTML is magical!

+/// ``` +class HTML {} diff --git a/packages/excerpter/test_data/example/plaster.dart b/packages/excerpter/test_data/example/plaster.dart new file mode 100644 index 0000000000..d9054f7c89 --- /dev/null +++ b/packages/excerpter/test_data/example/plaster.dart @@ -0,0 +1,44 @@ +// #docregion single +void single() { + // #enddocregion single + print('Not showing up!'); + // #docregion single +} +// #enddocregion single + +// #docregion multiple +void multiple() { + // #enddocregion multiple + print('Not showing up!'); + // #docregion multiple + print('Showing up!'); + // #enddocregion multiple + print('Not showing up!'); + // #docregion multiple +} +// #enddocregion multiple + +// #docregion remove +void remove() { + // #enddocregion remove + print('Nothing here.'); + // #docregion remove +} +// #enddocregion remove + +// #docregion custom +void custom() { + // #enddocregion custom + print('Different plaster here.'); + // #docregion custom +} +// #enddocregion custom + +// #docregion template +void template() { + // #enddocregion template + print('Templated plaster here.'); + // #docregion template +} + +// #enddocregion template diff --git a/packages/excerpter/test_data/example/simple_main.dart b/packages/excerpter/test_data/example/simple_main.dart new file mode 100644 index 0000000000..f4c4774e41 --- /dev/null +++ b/packages/excerpter/test_data/example/simple_main.dart @@ -0,0 +1,3 @@ +void main() { + print('Hello world!'); +} diff --git a/packages/excerpter/test_data/example/simple_region.dart b/packages/excerpter/test_data/example/simple_region.dart new file mode 100644 index 0000000000..bb04da5330 --- /dev/null +++ b/packages/excerpter/test_data/example/simple_region.dart @@ -0,0 +1,5 @@ +void main() { + // #docregion hello + print('Hello world!'); + // #enddocregion hello +} diff --git a/packages/excerpter/test_data/example/transforms.dart b/packages/excerpter/test_data/example/transforms.dart new file mode 100644 index 0000000000..4b1f5b573b --- /dev/null +++ b/packages/excerpter/test_data/example/transforms.dart @@ -0,0 +1,6 @@ +// #docregion indent +void indent() { + print('indent'); +} + +// #enddocregion indent diff --git a/packages/excerpter/test_data/expected/dartdoc.md b/packages/excerpter/test_data/expected/dartdoc.md new file mode 100644 index 0000000000..9e98cd711b --- /dev/null +++ b/packages/excerpter/test_data/expected/dartdoc.md @@ -0,0 +1,12 @@ +## API docs + +Markdown code block opened and closed with more than 3 backticks +that contains another Markdown code block declared with just 3. + + +````dart +/// ```html +///

HTML is magical!

+/// ``` +class HTML {} +```` diff --git a/packages/excerpter/test_data/expected/plaster.md b/packages/excerpter/test_data/expected/plaster.md new file mode 100644 index 0000000000..41b0476215 --- /dev/null +++ b/packages/excerpter/test_data/expected/plaster.md @@ -0,0 +1,46 @@ +## Test plaster feature + +### Single plaster + + +```dart +void single() { + // ... +} +``` + +### Multiple plaster + + +```dart +void multiple() { + // ... + print('Showing up!'); + // ... +} +``` + +### Remove plaster + + +```dart +void remove() { +} +``` + +### Custom template + + +```dart +void custom() { + /*...*/ +} +``` + + +```dart +void template() { + /* ... */ +} + +``` diff --git a/packages/excerpter/test_data/expected/simple.md b/packages/excerpter/test_data/expected/simple.md new file mode 100644 index 0000000000..6cd57b20d7 --- /dev/null +++ b/packages/excerpter/test_data/expected/simple.md @@ -0,0 +1,17 @@ +## Simple usages + +Simple excerpt with no region: + + +```dart +void main() { + print('Hello world!'); +} +``` + +Simple excerpt with region: + + +```dart +print('Hello world!'); +``` diff --git a/packages/excerpter/test_data/expected/transforms.md b/packages/excerpter/test_data/expected/transforms.md new file mode 100644 index 0000000000..bc7b913028 --- /dev/null +++ b/packages/excerpter/test_data/expected/transforms.md @@ -0,0 +1,11 @@ +## Transformations + +### Indentation + + +```dart + void indent() { + print('indent'); + } + +``` diff --git a/packages/excerpter/test_data/src/dartdoc.md b/packages/excerpter/test_data/src/dartdoc.md new file mode 100644 index 0000000000..c23b80ea5e --- /dev/null +++ b/packages/excerpter/test_data/src/dartdoc.md @@ -0,0 +1,8 @@ +## API docs + +Markdown code block opened and closed with more than 3 backticks +that contains another Markdown code block declared with just 3. + + +````dart +```` diff --git a/packages/excerpter/test_data/src/plaster.md b/packages/excerpter/test_data/src/plaster.md new file mode 100644 index 0000000000..4d6bfaad3f --- /dev/null +++ b/packages/excerpter/test_data/src/plaster.md @@ -0,0 +1,29 @@ +## Test plaster feature + +### Single plaster + + +```dart +``` + +### Multiple plaster + + +```dart +``` + +### Remove plaster + + +```dart +``` + +### Custom template + + +```dart +``` + + +```dart +``` diff --git a/packages/excerpter/test_data/src/simple.md b/packages/excerpter/test_data/src/simple.md new file mode 100644 index 0000000000..45288b92c1 --- /dev/null +++ b/packages/excerpter/test_data/src/simple.md @@ -0,0 +1,13 @@ +## Simple usages + +Simple excerpt with no region: + + +```dart +``` + +Simple excerpt with region: + + +```dart +``` diff --git a/packages/excerpter/test_data/src/transforms.md b/packages/excerpter/test_data/src/transforms.md new file mode 100644 index 0000000000..af7e872dfd --- /dev/null +++ b/packages/excerpter/test_data/src/transforms.md @@ -0,0 +1,7 @@ +## Transformations + +### Indentation + + +```dart +``` diff --git a/prompts/llmstxt_instructions.md b/prompts/llmstxt_instructions.md deleted file mode 100644 index e0df2c861b..0000000000 --- a/prompts/llmstxt_instructions.md +++ /dev/null @@ -1,63 +0,0 @@ -# System Instructions for Updating llms.txt - -## My role - -I am Gemini, an AI assistant. My role is to help maintain the `llms.txt` file in this repository. - -## My goal - -My goal is to ensure that the `llms.txt` file is an accurate, up-to-date, and well-organized list of resources to help Large Language Models (LLMs) understand the Flutter framework. - -## How should I handle updates - -When the user asks for updates to the `llms.txt` file, I will follow these instructions. - -### Adding a new section - -When the user asks me to add a new section, I will: -1. Ask for the title of the section and the links to include. -2. Add the new section to the file in a logical location. -3. Propose the changes to the user for review before committing. - -### Adding links to an existing section - -When asked to add new links to an existing section, I will: -1. Ask for the section and the links to add. -2. Add the links to the specified section. -3. Propose the changes to the user for review before committing. - -### Correcting information - -When asked to correct information, I will: -1. Ask for the incorrect information and the correction. -2. Update the file with the correct information. -3. Propose the changes to the user for review before committing. - -### Reviewing the file - -When asked to review the file, I will: -1. Read the entire file. -2. Check for duplicate entries, broken links, and areas for improvement. -3. Suggest any improvements to the user. - -### Committing and pushing changes - -After making any changes, I will: -1. Stage the proposed changes. -2. Always provide a clear and descriptive commit message to the user - and provide the URL of the staged version. -3. Wait for the user's approval before committing the changes. -4. After committing, I will ask the user if they want me to push the changes to the remote repository. -5. I will only push when the user explicitly tells me to. - -### Error Handling and Self-Correction - -If I encounter an error while performing an operation (for example, a tool fails or a command produces an unexpected output), I will: -1. Inform you about the error. -2. Analyze the error and try to understand the cause. -3. Propose a different approach or a solution. -4. I will not try to work around issues without your consent. - ---- - -By following these instructions, I will help keep the `llms.txt` file accurate and up-to-date. diff --git a/pubspec.yaml b/pubspec.yaml index bacdf43b4e..23981e17e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,8 +2,10 @@ name: docs_flutter_dev publish_to: none environment: - sdk: ^3.10.0 + sdk: ^3.11.0 workspace: + - packages/analysis_defaults + - packages/excerpter - site - tool/dash_site diff --git a/site/lib/_sass/base/_base.scss b/site/lib/_sass/base/_base.scss index 0053564614..181c9d0540 100644 --- a/site/lib/_sass/base/_base.scss +++ b/site/lib/_sass/base/_base.scss @@ -466,6 +466,7 @@ p+dl { display: flex; flex-direction: column; gap: 1rem; + margin-block-end: 1.25rem; align-items: center; .video-intro { @@ -477,7 +478,8 @@ p+dl { lite-youtube { border-radius: 0.25rem; - border: 2px solid rgba(0, 0, 0, .125); + border: 2px solid var(--site-outline-variant); + max-width: 760px; } } diff --git a/site/lib/src/components/common/button.dart b/site/lib/src/components/common/button.dart index 6736d1ecbb..740af24cf3 100644 --- a/site/lib/src/components/common/button.dart +++ b/site/lib/src/components/common/button.dart @@ -82,8 +82,7 @@ class Button extends StatelessComponent { enum ButtonStyle { filled, outlined, - text - ; + text; String get cssClass => switch (this) { ButtonStyle.filled => 'filled-button', diff --git a/site/lib/src/components/common/client/feedback.dart b/site/lib/src/components/common/client/feedback.dart index 95fcc1f48f..71219a0038 100644 --- a/site/lib/src/components/common/client/feedback.dart +++ b/site/lib/src/components/common/client/feedback.dart @@ -83,8 +83,7 @@ enum _FeedbackState { unhelpful( '感谢你的反馈!' '欢迎告诉我们该如何改进。', - ) - ; + ); const _FeedbackState(this.introduction); diff --git a/site/lib/src/components/common/wrapped_code_block.dart b/site/lib/src/components/common/wrapped_code_block.dart index 155ad4bcb1..3bc7596c14 100644 --- a/site/lib/src/components/common/wrapped_code_block.dart +++ b/site/lib/src/components/common/wrapped_code_block.dart @@ -168,8 +168,7 @@ enum CodeBlockTag { passesStaticAnalysis('static analysis: success', parentClass: 'passes-sa'), failsStaticAnalysis('static analysis: failure', parentClass: 'fails-sa'), runtimeSuccess('runtime: success', parentClass: 'runtime-success'), - runtimeFailure('runtime: failure', parentClass: 'runtime-fail') - ; + runtimeFailure('runtime: failure', parentClass: 'runtime-fail'); const CodeBlockTag(this.spanContent, {required this.parentClass}); diff --git a/site/lib/src/components/layout/header.dart b/site/lib/src/components/layout/header.dart index fdb95a8fa6..a8a2dfe42f 100644 --- a/site/lib/src/components/layout/header.dart +++ b/site/lib/src/components/layout/header.dart @@ -54,7 +54,7 @@ class DashHeader extends StatelessComponent { ul(classes: 'nav-items', [ _NavItem( href: '/', - label: '用户指南', + label: '指南', isActive: activeEntry == ActiveNavEntry.home, ), _NavItem( diff --git a/site/lib/src/components/layout/sidenav.dart b/site/lib/src/components/layout/sidenav.dart index 9d2e8c5e5a..eee4c6142e 100644 --- a/site/lib/src/components/layout/sidenav.dart +++ b/site/lib/src/components/layout/sidenav.dart @@ -30,28 +30,26 @@ final class DashSideNav extends StatelessComponent { final activeEntry = activeNavEntry(context.page.url); return div(id: 'sidenav', [ - // Only show the nav items if the tutorial is active for now. - if (activeEntry == ActiveNavEntry.learn) - ul(classes: 'navbar-nav', [ - _TopNavItem( - href: '/', - label: '主页', - iconId: 'asterisk', - active: activeEntry == ActiveNavEntry.home, - ), - _TopNavItem( - href: '/learn', - label: '学习', - iconId: 'play_lesson', - active: activeEntry == ActiveNavEntry.learn, - ), - const _TopNavItem( - href: 'https://api.flutter-io.cn', - label: 'API 参考', - iconId: 'api', - ), - const _SideNavDivider(), - ]), + ul(classes: 'navbar-nav', [ + _TopNavItem( + href: '/', + label: '指南', + iconId: 'asterisk', + active: activeEntry == ActiveNavEntry.home, + ), + _TopNavItem( + href: '/learn', + label: '学习', + iconId: 'play_lesson', + active: activeEntry == ActiveNavEntry.learn, + ), + const _TopNavItem( + href: 'https://api.flutter-io.cn', + label: 'API 参考', + iconId: 'api', + ), + const _SideNavDivider(), + ]), nav([ _SideNavLevel( diff --git a/site/lib/src/components/layout/theme_switcher.dart b/site/lib/src/components/layout/theme_switcher.dart index e2710a4b33..e3ac0f85a7 100644 --- a/site/lib/src/components/layout/theme_switcher.dart +++ b/site/lib/src/components/layout/theme_switcher.dart @@ -21,8 +21,7 @@ final class ThemeSwitcher extends StatefulComponent { enum _Theme { light('浅色', 'Switch to the light theme.', 'light_mode'), dark('深色', 'Switch to the dark theme.', 'dark_mode'), - auto('跟随系统', 'Match theme to device theme.', 'night_sight_auto') - ; + auto('跟随系统', 'Match theme to device theme.', 'night_sight_auto'); final String label; final String description; diff --git a/site/lib/src/components/pages/widget_catalog.dart b/site/lib/src/components/pages/widget_catalog.dart index 9bf24bf125..61e968225b 100644 --- a/site/lib/src/components/pages/widget_catalog.dart +++ b/site/lib/src/components/pages/widget_catalog.dart @@ -115,7 +115,7 @@ class WidgetCatalogCard extends StatelessComponent { return a(href: widget.link, classes: 'card outlined-card', [ _buildCardImageHolder(), div(classes: 'card-header', [ - span(classes: 'card-title', [.text(widget.name)]), + span(classes: 'card-title', _splitCamelCase(widget.name)), ]), div(classes: 'card-content', [ p([ @@ -184,3 +184,50 @@ class WidgetCatalogCard extends StatelessComponent { ); } } + +class WidgetCardGrid extends StatelessComponent { + const WidgetCardGrid({ + required this.widgets, + required this.isMaterialCatalog, + this.subcategory, + }); + + final List widgets; + final bool isMaterialCatalog; + final WidgetCatalogSubcategory? subcategory; + + @override + Component build(BuildContext _) => div( + classes: [ + 'card-grid', + if (isMaterialCatalog) 'material-cards', + ].toClasses, + [ + for (final widget in widgets) + WidgetCatalogCard( + widget: widget, + isMaterialCatalog: isMaterialCatalog, + subcategory: subcategory, + ), + ], + ); +} + +final RegExp _camelCaseSplitRegex = RegExp( + r'(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])', +); + +/// Splits an UpperCamelCase [name] into its constituent parts, +/// interleaving `` elements to allow the browser to +/// break long widget names across lines. +List _splitCamelCase(String name) { + final parts = name.split(_camelCaseSplitRegex); + if (parts.length <= 1) return [.text(name)]; + return [ + .text(parts.first), + for (var partIndex = 1; partIndex < parts.length; partIndex += 1) ...[ + const wbr(), + .text(parts[partIndex]), + ], + ]; +} diff --git a/site/lib/src/highlight/token_renderer.dart b/site/lib/src/highlight/token_renderer.dart index 175e06e2fc..e158c57e92 100644 --- a/site/lib/src/highlight/token_renderer.dart +++ b/site/lib/src/highlight/token_renderer.dart @@ -152,8 +152,7 @@ enum FontWeight { w600(600), w700(700), w800(800), - w900(900) - ; + w900(900); final int weight; diff --git a/site/lib/src/markdown/alert_syntax.dart b/site/lib/src/markdown/alert_syntax.dart index 1b64303904..519183cdcc 100644 --- a/site/lib/src/markdown/alert_syntax.dart +++ b/site/lib/src/markdown/alert_syntax.dart @@ -157,8 +157,7 @@ enum _AlertType { ), secondary( cssClass: 'alert-secondary', - ) - ; + ); /// The CSS class to add to `aside` element final String cssClass; diff --git a/site/lib/src/models/learning_resource_model.dart b/site/lib/src/models/learning_resource_model.dart index 68e248bb11..12e0224df1 100644 --- a/site/lib/src/models/learning_resource_model.dart +++ b/site/lib/src/models/learning_resource_model.dart @@ -57,8 +57,7 @@ enum LearningResourceType { tutorial('Tutorial', ['codelab', 'tutorial']), sampleCode('Sample code', ['quickstart', 'demo', 'sample', 'sample code']), workshop('Workshop', ['workshop', 'video']), - recipe('Recipe', ['recipe', 'how to', 'cookbook']) - ; + recipe('Recipe', ['recipe', 'how to', 'cookbook']); const LearningResourceType(this.label, this.tags); @@ -100,8 +99,7 @@ enum LearningResourceTag { ]), testing('Testing', ['testing', 'tests', 'test', 'perf', 'performance']), web('Web', ['web', 'wasm']), - widgets('Widgets', ['widgets', 'layout']) - ; + widgets('Widgets', ['widgets', 'layout']); const LearningResourceTag(this.label, this.tags); diff --git a/site/lib/src/models/quiz_model.dart b/site/lib/src/models/quiz_model.dart index cdb9beca3a..97f00909cf 100644 --- a/site/lib/src/models/quiz_model.dart +++ b/site/lib/src/models/quiz_model.dart @@ -16,7 +16,8 @@ class Question { json['question'] as String, (json['options'] as List) .map((e) => AnswerOption.fromJson(e as Map)) - .toList(), + .toList() + ..shuffle(), ); } diff --git a/site/lib/src/pages/glossary.dart b/site/lib/src/pages/glossary.dart index 9e9e30c1fa..c3a2590159 100644 --- a/site/lib/src/pages/glossary.dart +++ b/site/lib/src/pages/glossary.dart @@ -21,8 +21,7 @@ enum ResourceType { video, code, diagnostic, - external - ; + external; /// The ID of the material symbol icon associated with each resource type. String get icon => switch (this) { diff --git a/site/lib/src/pages/widget_catalog.dart b/site/lib/src/pages/widget_catalog.dart index 51cc4d222c..78c5221571 100644 --- a/site/lib/src/pages/widget_catalog.dart +++ b/site/lib/src/pages/widget_catalog.dart @@ -66,8 +66,8 @@ List get widgetCatalogPages { // Only show main category widgets for non-material catalogs. if (!isMaterialCatalog && widgetsInCategory.isNotEmpty) - _buildCardGrid( - widgetsInCategory, + WidgetCardGrid( + widgets: widgetsInCategory, isMaterialCatalog: isMaterialCatalog, ), @@ -166,31 +166,10 @@ List _buildSubcategorySection( return [ h2(id: slugify(subName), [.text(subName)]), - _buildCardGrid( - widgets, + WidgetCardGrid( + widgets: widgets, isMaterialCatalog: isMaterialCatalog, subcategory: subcategory, ), ]; } - -Component _buildCardGrid( - List widgets, { - required bool isMaterialCatalog, - WidgetCatalogSubcategory? subcategory, -}) { - return div( - classes: [ - 'card-grid', - if (isMaterialCatalog) 'material-cards', - ].toClasses, - [ - for (final widget in widgets) - WidgetCatalogCard( - widget: widget, - isMaterialCatalog: isMaterialCatalog, - subcategory: subcategory, - ), - ], - ); -} diff --git a/site/lib/src/style_hash.dart b/site/lib/src/style_hash.dart index 0a86c7f940..ce114383cc 100644 --- a/site/lib/src/style_hash.dart +++ b/site/lib/src/style_hash.dart @@ -2,4 +2,4 @@ // dart format off /// The generated hash of the `main.css` file. -const generatedStylesHash = 'D1OovaDfTpf7'; +const generatedStylesHash = ''; diff --git a/site/lib/src/util.dart b/site/lib/src/util.dart index 2cba99ef56..5ad8e58799 100644 --- a/site/lib/src/util.dart +++ b/site/lib/src/util.dart @@ -123,8 +123,7 @@ enum OperatingSystem { windows('Windows'), macos('macOS'), linux('Linux'), - chromeos('ChromeOS') - ; + chromeos('ChromeOS'); const OperatingSystem(this.label); final String label; diff --git a/site/pubspec.yaml b/site/pubspec.yaml index 56519ed196..e037c9f2bc 100644 --- a/site/pubspec.yaml +++ b/site/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://docs.flutter.dev resolution: workspace environment: - sdk: ^3.10.0 + sdk: ^3.11.0 dependencies: build: ^4.0.4 @@ -12,8 +12,8 @@ dependencies: crypto: ^3.0.7 html: ^0.15.6 http: ^1.6.0 - jaspr: ^0.22.2 - jaspr_content: ^0.4.5 + jaspr: ^0.22.3 + jaspr_content: ^0.5.0 # Used as our template engine. liquify: 1.3.1 markdown: ^7.3.0 @@ -29,13 +29,10 @@ dependencies: dev_dependencies: analysis_defaults: - git: - url: https://github.com/dart-lang/site-shared - path: pkgs/analysis_defaults - ref: f91ed8ecef6a0b31685804fe4102b25fda021460 + path: ../packages/analysis_defaults build_runner: ^2.11.0 build_web_compilers: ^4.4.9 - jaspr_builder: ^0.22.2 + jaspr_builder: ^0.22.3 sass: ^1.97.3 sass_builder: ^2.4.0 diff --git a/site/web/assets/images/docs/breaking-changes/disable-ui-scene.png b/site/web/assets/images/docs/breaking-changes/disable-ui-scene.png new file mode 100644 index 0000000000..3451a70126 Binary files /dev/null and b/site/web/assets/images/docs/breaking-changes/disable-ui-scene.png differ diff --git a/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/add-files-to-runner.png b/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/add-files-to-runner.png new file mode 100644 index 0000000000..bc126b8bca Binary files /dev/null and b/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/add-files-to-runner.png differ diff --git a/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/copy-full-path.png b/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/copy-full-path.png new file mode 100644 index 0000000000..7ec48f18ce Binary files /dev/null and b/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/copy-full-path.png differ diff --git a/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/reference-files-in-place.png b/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/reference-files-in-place.png new file mode 100644 index 0000000000..d9e8d3c80b Binary files /dev/null and b/site/web/assets/images/docs/development/packages-and-plugins/swift-package-manager/reference-files-in-place.png differ diff --git a/src/_includes/docs/get-started/setup-next-steps.html b/src/_includes/docs/get-started/setup-next-steps.html index c7a27ca0fb..a2ae390101 100644 --- a/src/_includes/docs/get-started/setup-next-steps.html +++ b/src/_includes/docs/get-started/setup-next-steps.html @@ -38,9 +38,6 @@