mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Merge branch 'main' of https://github.com/flutter/samples into cupertino-gallery
This commit is contained in:
11
.gemini/settings.json
Normal file
11
.gemini/settings.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"dart": {
|
||||||
|
"command": "dart",
|
||||||
|
"args": [
|
||||||
|
"mcp-server"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contextFileName": "/.prompts/llm.md"
|
||||||
|
}
|
||||||
52
.github/workflows/beta.yml
vendored
52
.github/workflows/beta.yml
vendored
@@ -15,10 +15,10 @@ defaults:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Run the stable test script on the beta channel. Since this branch will soon
|
# Test all samples on the beta channel. Since the beta channel will soon be
|
||||||
# be merged into main as our stable-targeting code, this is the key thing we
|
# promoted to stable, this branch is only concerned with the beta.
|
||||||
# need to test.
|
Beta-CI:
|
||||||
stable-tests-on-beta:
|
name: Test flutter beta channel
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
if: github.repository == 'flutter/samples'
|
if: github.repository == 'flutter/samples'
|
||||||
strategy:
|
strategy:
|
||||||
@@ -27,43 +27,43 @@ jobs:
|
|||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00
|
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
||||||
with:
|
with:
|
||||||
channel: beta
|
channel: beta
|
||||||
- run: ./tool/flutter_ci_script_stable.sh
|
- run: flutter pub get && dart tool/ci_script.dart
|
||||||
|
|
||||||
# Verify the Android add-to-app samples build and pass tests with the beta
|
# Verify the Android add-to-app samples build and pass tests with the beta
|
||||||
# channel.
|
# channel.
|
||||||
# android-build:
|
android-build:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'flutter/samples'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
|
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: '17'
|
||||||
|
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
||||||
|
with:
|
||||||
|
channel: beta
|
||||||
|
- run: ./tool/android_ci_script.sh
|
||||||
|
|
||||||
|
# Verify the iOS add-to-app samples build and pass tests with the beta
|
||||||
|
# channel.
|
||||||
|
# ios-build:
|
||||||
|
# runs-on: macos-latest
|
||||||
# if: github.repository == 'flutter/samples'
|
# if: github.repository == 'flutter/samples'
|
||||||
# steps:
|
# steps:
|
||||||
# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
# - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00
|
# - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165
|
||||||
# with:
|
# with:
|
||||||
# distribution: 'zulu'
|
# distribution: 'zulu'
|
||||||
# java-version: '17'
|
# java-version: '17'
|
||||||
# - uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
# - uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
||||||
# with:
|
# with:
|
||||||
# channel: beta
|
# channel: beta
|
||||||
# - run: ./tool/android_ci_script.sh
|
# - run: ./tool/ios_ci_script.sh
|
||||||
|
|
||||||
# Verify the iOS add-to-app samples build and pass tests with the beta
|
|
||||||
# channel.
|
|
||||||
ios-build:
|
|
||||||
runs-on: macos-latest
|
|
||||||
if: github.repository == 'flutter/samples'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00
|
|
||||||
with:
|
|
||||||
distribution: 'zulu'
|
|
||||||
java-version: '17'
|
|
||||||
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
|
||||||
with:
|
|
||||||
channel: beta
|
|
||||||
- run: ./tool/ios_ci_script.sh
|
|
||||||
|
|||||||
30
.github/workflows/build-android.yml
vendored
Normal file
30
.github/workflows/build-android.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Test Android Build
|
||||||
|
|
||||||
|
# Declare default permissions as read only.
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [beta]
|
||||||
|
pull_request:
|
||||||
|
branches: [beta]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
android-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'flutter/samples'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
|
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: '17'
|
||||||
|
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
- run: ./tool/android_ci_script.sh
|
||||||
33
.github/workflows/build-ios.yml
vendored
Normal file
33
.github/workflows/build-ios.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: Test iOS Build
|
||||||
|
|
||||||
|
# Declare default permissions as read only.
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [beta]
|
||||||
|
pull_request:
|
||||||
|
branches: [beta]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ios-build:
|
||||||
|
name: Test flutter beta channel
|
||||||
|
runs-on: macos-latest
|
||||||
|
if: github.repository == 'flutter/samples'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
|
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: '17'
|
||||||
|
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
||||||
|
with:
|
||||||
|
channel: beta
|
||||||
|
- run: ./tool/ios_ci_script.sh
|
||||||
331
.github/workflows/gemini-cli.yml
vendored
Normal file
331
.github/workflows/gemini-cli.yml
vendored
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
name: '💬 Gemini CLI'
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_review_comment:
|
||||||
|
types:
|
||||||
|
- 'created'
|
||||||
|
pull_request_review:
|
||||||
|
types:
|
||||||
|
- 'submitted'
|
||||||
|
issue_comment:
|
||||||
|
types:
|
||||||
|
- 'created'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.event.issue.number }}'
|
||||||
|
cancel-in-progress: |-
|
||||||
|
${{ github.event.sender.type == 'User' && ( github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'COLLABORATOR') }}
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: 'bash'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: 'write'
|
||||||
|
id-token: 'write'
|
||||||
|
pull-requests: 'write'
|
||||||
|
issues: 'write'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
gemini-cli:
|
||||||
|
# This condition is complex to ensure we only run when explicitly invoked.
|
||||||
|
if: |-
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(
|
||||||
|
github.event_name == 'issues' && github.event.action == 'opened' &&
|
||||||
|
contains(github.event.issue.body, '@gemini-cli') &&
|
||||||
|
!contains(github.event.issue.body, '/review') &&
|
||||||
|
!contains(github.event.issue.body, '/triage') &&
|
||||||
|
(
|
||||||
|
github.event.sender.type == 'User' && (
|
||||||
|
github.event.issue.author_association == 'OWNER' ||
|
||||||
|
github.event.issue.author_association == 'MEMBER' ||
|
||||||
|
github.event.issue.author_association == 'COLLABORATOR'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
github.event_name == 'issue_comment' &&
|
||||||
|
contains(github.event.comment.body, '@gemini-cli') &&
|
||||||
|
!contains(github.event.comment.body, '/review') &&
|
||||||
|
!contains(github.event.comment.body, '/triage') &&
|
||||||
|
(
|
||||||
|
github.event.sender.type == 'User' && (
|
||||||
|
github.event.comment.author_association == 'OWNER' ||
|
||||||
|
github.event.comment.author_association == 'MEMBER' ||
|
||||||
|
github.event.comment.author_association == 'COLLABORATOR'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
github.event_name == 'pull_request_review' &&
|
||||||
|
contains(github.event.review.body, '@gemini-cli') &&
|
||||||
|
!contains(github.event.review.body, '/review') &&
|
||||||
|
!contains(github.event.review.body, '/triage') &&
|
||||||
|
(
|
||||||
|
github.event.sender.type == 'User' && (
|
||||||
|
github.event.review.author_association == 'OWNER' ||
|
||||||
|
github.event.review.author_association == 'MEMBER' ||
|
||||||
|
github.event.review.author_association == 'COLLABORATOR'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
github.event_name == 'pull_request_review_comment' &&
|
||||||
|
contains(github.event.comment.body, '@gemini-cli') &&
|
||||||
|
!contains(github.event.comment.body, '/review') &&
|
||||||
|
!contains(github.event.comment.body, '/triage') &&
|
||||||
|
(
|
||||||
|
github.event.sender.type == 'User' && (
|
||||||
|
github.event.comment.author_association == 'OWNER' ||
|
||||||
|
github.event.comment.author_association == 'MEMBER' ||
|
||||||
|
github.event.comment.author_association == 'COLLABORATOR'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
timeout-minutes: 10
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Generate GitHub App Token'
|
||||||
|
id: 'generate_token'
|
||||||
|
if: |-
|
||||||
|
${{ vars.APP_ID }}
|
||||||
|
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # ratchet:actions/create-github-app-token@v2
|
||||||
|
with:
|
||||||
|
app-id: '${{ vars.APP_ID }}'
|
||||||
|
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
|
||||||
|
|
||||||
|
- name: 'Get context from event'
|
||||||
|
id: 'get_context'
|
||||||
|
env:
|
||||||
|
EVENT_NAME: '${{ github.event_name }}'
|
||||||
|
EVENT_PAYLOAD: '${{ toJSON(github.event) }}'
|
||||||
|
run: |-
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
USER_REQUEST=""
|
||||||
|
ISSUE_NUMBER=""
|
||||||
|
IS_PR="false"
|
||||||
|
|
||||||
|
if [[ "${EVENT_NAME}" == "issues" ]]; then
|
||||||
|
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .issue.body)
|
||||||
|
ISSUE_NUMBER=$(echo "${EVENT_PAYLOAD}" | jq -r .issue.number)
|
||||||
|
elif [[ "${EVENT_NAME}" == "issue_comment" ]]; then
|
||||||
|
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .comment.body)
|
||||||
|
ISSUE_NUMBER=$(echo "${EVENT_PAYLOAD}" | jq -r .issue.number)
|
||||||
|
if [[ $(echo "${EVENT_PAYLOAD}" | jq -r .issue.pull_request) != "null" ]]; then
|
||||||
|
IS_PR="true"
|
||||||
|
fi
|
||||||
|
elif [[ "${EVENT_NAME}" == "pull_request_review" ]]; then
|
||||||
|
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .review.body)
|
||||||
|
ISSUE_NUMBER=$(echo "${EVENT_PAYLOAD}" | jq -r .pull_request.number)
|
||||||
|
IS_PR="true"
|
||||||
|
elif [[ "${EVENT_NAME}" == "pull_request_review_comment" ]]; then
|
||||||
|
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .comment.body)
|
||||||
|
ISSUE_NUMBER=$(echo "${EVENT_PAYLOAD}" | jq -r .pull_request.number)
|
||||||
|
IS_PR="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up user request
|
||||||
|
USER_REQUEST=$(echo "${USER_REQUEST}" | sed 's/.*@gemini-cli//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "user_request=${USER_REQUEST}"
|
||||||
|
echo "issue_number=${ISSUE_NUMBER}"
|
||||||
|
echo "is_pr=${IS_PR}"
|
||||||
|
} >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
- name: 'Set up git user for commits'
|
||||||
|
run: |-
|
||||||
|
git config --global user.name 'gemini-cli[bot]'
|
||||||
|
git config --global user.email 'gemini-cli[bot]@users.noreply.github.com'
|
||||||
|
|
||||||
|
- name: 'Checkout PR branch'
|
||||||
|
if: |-
|
||||||
|
${{ steps.get_context.outputs.is_pr == 'true' }}
|
||||||
|
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
repository: '${{ github.repository }}'
|
||||||
|
ref: 'refs/pull/${{ steps.get_context.outputs.issue_number }}/head'
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 'Checkout main branch'
|
||||||
|
if: |-
|
||||||
|
${{ steps.get_context.outputs.is_pr == 'false' }}
|
||||||
|
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
repository: '${{ github.repository }}'
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 'Acknowledge request'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
|
||||||
|
REPOSITORY: '${{ github.repository }}'
|
||||||
|
REQUEST_TYPE: '${{ steps.get_context.outputs.request_type }}'
|
||||||
|
run: |-
|
||||||
|
set -euo pipefail
|
||||||
|
MESSAGE="I've received your request and I'm working on it now! 🤖"
|
||||||
|
if [[ -n "${MESSAGE}" ]]; then
|
||||||
|
gh issue comment "${ISSUE_NUMBER}" \
|
||||||
|
--body "${MESSAGE}" \
|
||||||
|
--repo "${REPOSITORY}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: 'Get description'
|
||||||
|
id: 'get_description'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
IS_PR: '${{ steps.get_context.outputs.is_pr }}'
|
||||||
|
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
|
||||||
|
run: |-
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ "${IS_PR}" == "true" ]]; then
|
||||||
|
DESCRIPTION=$(gh pr view "${ISSUE_NUMBER}" --json body --template '{{.body}}')
|
||||||
|
else
|
||||||
|
DESCRIPTION=$(gh issue view "${ISSUE_NUMBER}" --json body --template '{{.body}}')
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "description<<EOF"
|
||||||
|
echo "${DESCRIPTION}"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
- name: 'Get comments'
|
||||||
|
id: 'get_comments'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
IS_PR: '${{ steps.get_context.outputs.is_pr }}'
|
||||||
|
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
|
||||||
|
run: |-
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ "${IS_PR}" == "true" ]]; then
|
||||||
|
COMMENTS=$(gh pr view "${ISSUE_NUMBER}" --json comments --template '{{range .comments}}{{.author.login}}: {{.body}}{{"\n"}}{{end}}')
|
||||||
|
else
|
||||||
|
COMMENTS=$(gh issue view "${ISSUE_NUMBER}" --json comments --template '{{range .comments}}{{.author.login}}: {{.body}}{{"\n"}}{{end}}')
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "comments<<EOF"
|
||||||
|
echo "${COMMENTS}"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
- name: 'Run Gemini'
|
||||||
|
id: 'run_gemini'
|
||||||
|
uses: 'google-github-actions/run-gemini-cli@v0'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
REPOSITORY: '${{ github.repository }}'
|
||||||
|
USER_REQUEST: '${{ steps.get_context.outputs.user_request }}'
|
||||||
|
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
|
||||||
|
IS_PR: '${{ steps.get_context.outputs.is_pr }}'
|
||||||
|
with:
|
||||||
|
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||||
|
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
|
||||||
|
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
|
||||||
|
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
|
||||||
|
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||||
|
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
|
||||||
|
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
|
||||||
|
settings: |-
|
||||||
|
{
|
||||||
|
"maxSessionTurns": 50,
|
||||||
|
"telemetry": {
|
||||||
|
"enabled": false,
|
||||||
|
"target": "gcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prompt: |-
|
||||||
|
## Role
|
||||||
|
|
||||||
|
You are a helpful AI assistant invoked via a CLI interface in a GitHub workflow. You have access to tools to interact with the repository and respond to the user.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- **Repository**: `${{ github.repository }}`
|
||||||
|
- **Triggering Event**: `${{ github.event_name }}`
|
||||||
|
- **Issue/PR Number**: `${{ steps.get_context.outputs.issue_number }}`
|
||||||
|
- **Is this a PR?**: `${{ steps.get_context.outputs.is_pr }}`
|
||||||
|
- **Issue/PR Description**:
|
||||||
|
`${{ steps.get_description.outputs.description }}`
|
||||||
|
- **Comments**:
|
||||||
|
`${{ steps.get_comments.outputs.comments }}`
|
||||||
|
|
||||||
|
## User Request
|
||||||
|
|
||||||
|
The user has sent the following request:
|
||||||
|
`${{ steps.get_context.outputs.user_request }}`
|
||||||
|
|
||||||
|
## How to Respond to Issues, PR Comments, and Questions
|
||||||
|
|
||||||
|
This workflow supports three main scenarios:
|
||||||
|
|
||||||
|
1. **Creating a Fix for an Issue**
|
||||||
|
- Carefully read the user request and the related issue or PR description.
|
||||||
|
- Use available tools to gather all relevant context (e.g., `gh issue view`, `gh pr view`, `gh pr diff`, `cat`, `head`, `tail`).
|
||||||
|
- Identify the root cause of the problem before proceeding.
|
||||||
|
- **Show and maintain a plan as a checklist**:
|
||||||
|
- At the very beginning, outline the steps needed to resolve the issue or address the request and post them as a checklist comment on the issue or PR (use GitHub markdown checkboxes: `- [ ] Task`).
|
||||||
|
- Example:
|
||||||
|
```
|
||||||
|
### Plan
|
||||||
|
- [ ] Investigate the root cause
|
||||||
|
- [ ] Implement the fix in `file.py`
|
||||||
|
- [ ] Add/modify tests
|
||||||
|
- [ ] Update documentation
|
||||||
|
- [ ] Verify the fix and close the issue
|
||||||
|
```
|
||||||
|
- Use: `gh pr comment "${ISSUE_NUMBER}" --body "<plan>"` or `gh issue comment "${ISSUE_NUMBER}" --body "<plan>"` to post the initial plan.
|
||||||
|
- As you make progress, keep the checklist visible and up to date by editing the same comment (check off completed tasks with `- [x]`).
|
||||||
|
- To update the checklist:
|
||||||
|
1. Find the comment ID for the checklist (use `gh pr comment list "${ISSUE_NUMBER}"` or `gh issue comment list "${ISSUE_NUMBER}"`).
|
||||||
|
2. Edit the comment with the updated checklist:
|
||||||
|
- For PRs: `gh pr comment --edit <comment-id> --body "<updated plan>"`
|
||||||
|
- For Issues: `gh issue comment --edit <comment-id> --body "<updated plan>"`
|
||||||
|
3. The checklist should only be maintained as a comment on the issue or PR. Do not track or update the checklist in code files.
|
||||||
|
- If the fix requires code changes, determine which files and lines are affected. If clarification is needed, note any questions for the user.
|
||||||
|
- Make the necessary code or documentation changes using the available tools (e.g., `write_file`). Ensure all changes follow project conventions and best practices. Reference all shell variables as `"${VAR}"` (with quotes and braces) to prevent errors.
|
||||||
|
- Run any relevant tests or checks to verify the fix works as intended. If possible, provide evidence (test output, screenshots, etc.) that the issue is resolved.
|
||||||
|
- **Branching and Committing**:
|
||||||
|
- **NEVER commit directly to the `main` branch.**
|
||||||
|
- If you are working on a **pull request** (`IS_PR` is `true`), the correct branch is already checked out. Simply commit and push to it.
|
||||||
|
- `git add .`
|
||||||
|
- `git commit -m "feat: <describe the change>"`
|
||||||
|
- `git push`
|
||||||
|
- If you are working on an **issue** (`IS_PR` is `false`), create a new branch for your changes. A good branch name would be `issue/${ISSUE_NUMBER}/<short-description>`.
|
||||||
|
- `git checkout -b issue/${ISSUE_NUMBER}/my-fix`
|
||||||
|
- `git add .`
|
||||||
|
- `git commit -m "feat: <describe the fix>"`
|
||||||
|
- `git push origin issue/${ISSUE_NUMBER}/my-fix`
|
||||||
|
- After pushing, you can create a pull request: `gh pr create --title "Fixes #${ISSUE_NUMBER}: <short title>" --body "This PR addresses issue #${ISSUE_NUMBER}."`
|
||||||
|
- Summarize what was changed and why in a markdown file: `write_file("response.md", "<your response here>")`
|
||||||
|
- Post the response as a comment:
|
||||||
|
- For PRs: `gh pr comment "${ISSUE_NUMBER}" --body-file response.md`
|
||||||
|
- For Issues: `gh issue comment "${ISSUE_NUMBER}" --body-file response.md`
|
||||||
|
|
||||||
|
2. **Addressing Comments on a Pull Request**
|
||||||
|
- Read the specific comment and the context of the PR.
|
||||||
|
- Use tools like `gh pr view`, `gh pr diff`, and `cat` to understand the code and discussion.
|
||||||
|
- If the comment requests a change or clarification, follow the same process as for fixing an issue: create a checklist plan, implement, test, and commit any required changes, updating the checklist as you go.
|
||||||
|
- **Committing Changes**: The correct PR branch is already checked out. Simply add, commit, and push your changes.
|
||||||
|
- `git add .`
|
||||||
|
- `git commit -m "fix: address review comments"`
|
||||||
|
- `git push`
|
||||||
|
- If the comment is a question, answer it directly and clearly, referencing code or documentation as needed.
|
||||||
|
- Document your response in `response.md` and post it as a PR comment: `gh pr comment "${ISSUE_NUMBER}" --body-file response.md`
|
||||||
|
|
||||||
|
3. **Answering Any Question on an Issue**
|
||||||
|
- Read the question and the full issue context using `gh issue view` and related tools.
|
||||||
|
- Research or analyze the codebase as needed to provide an accurate answer.
|
||||||
|
- If the question requires code or documentation changes, follow the fix process above, including creating and updating a checklist plan and **creating a new branch for your changes as described in section 1.**
|
||||||
|
- Write a clear, concise answer in `response.md` and post it as an issue comment: `gh issue comment "${ISSUE_NUMBER}" --body-file response.md`
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
|
||||||
|
- **Be concise and actionable.** Focus on solving the user's problem efficiently.
|
||||||
|
- **Always commit and push your changes if you modify code or documentation.**
|
||||||
|
- **If you are unsure about the fix or answer, explain your reasoning and ask clarifying questions.**
|
||||||
|
- **Follow project conventions and best practices.**
|
||||||
129
.github/workflows/gemini-issue-automated-triage.yml
vendored
Normal file
129
.github/workflows/gemini-issue-automated-triage.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
name: '🏷️ Gemini Automated Issue Triage'
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- 'opened'
|
||||||
|
- 'reopened'
|
||||||
|
issue_comment:
|
||||||
|
types:
|
||||||
|
- 'created'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
issue_number:
|
||||||
|
description: 'issue number to triage'
|
||||||
|
required: true
|
||||||
|
type: 'number'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.event.issue.number }}'
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: 'bash'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: 'read'
|
||||||
|
id-token: 'write'
|
||||||
|
issues: 'write'
|
||||||
|
statuses: 'write'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
triage-issue:
|
||||||
|
if: >
|
||||||
|
github.event_name == 'issues' ||
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event_name == 'issue_comment' &&
|
||||||
|
contains(github.event.comment.body, '@gemini-cli /triage') &&
|
||||||
|
(github.event.comment.author_association == 'OWNER' ||
|
||||||
|
github.event.comment.author_association == 'MEMBER' ||
|
||||||
|
github.event.comment.author_association == 'COLLABORATOR'))
|
||||||
|
timeout-minutes: 5
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout repository'
|
||||||
|
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 'Generate GitHub App Token'
|
||||||
|
id: 'generate_token'
|
||||||
|
if: |-
|
||||||
|
${{ vars.APP_ID }}
|
||||||
|
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # ratchet:actions/create-github-app-token@v2
|
||||||
|
with:
|
||||||
|
app-id: '${{ vars.APP_ID }}'
|
||||||
|
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
|
||||||
|
|
||||||
|
- name: 'Run Gemini Issue Triage'
|
||||||
|
uses: 'google-github-actions/run-gemini-cli@v0'
|
||||||
|
id: 'gemini_issue_triage'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
ISSUE_TITLE: '${{ github.event.issue.title }}'
|
||||||
|
ISSUE_BODY: '${{ github.event.issue.body }}'
|
||||||
|
ISSUE_NUMBER: '${{ github.event.issue.number }}'
|
||||||
|
REPOSITORY: '${{ github.repository }}'
|
||||||
|
with:
|
||||||
|
gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}'
|
||||||
|
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
|
||||||
|
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
|
||||||
|
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
|
||||||
|
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||||
|
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||||
|
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
|
||||||
|
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
|
||||||
|
settings: |-
|
||||||
|
{
|
||||||
|
"maxSessionTurns": 25,
|
||||||
|
"coreTools": [
|
||||||
|
"run_shell_command(gh label list)",
|
||||||
|
"run_shell_command(gh issue edit)"
|
||||||
|
],
|
||||||
|
"telemetry": {
|
||||||
|
"enabled": false,
|
||||||
|
"target": "gcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prompt: |-
|
||||||
|
## Role
|
||||||
|
|
||||||
|
You are an issue triage assistant. Analyze the current GitHub issue
|
||||||
|
and apply the most appropriate existing labels. Use the available
|
||||||
|
tools to gather information; do not ask for information to be
|
||||||
|
provided.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Run: `gh label list` to get all available labels.
|
||||||
|
2. Review the issue title and body provided in the environment
|
||||||
|
variables: "${ISSUE_TITLE}" and "${ISSUE_BODY}".
|
||||||
|
3. Select the most relevant labels from the existing labels. If
|
||||||
|
available, set labels that follow the `kind/*`, `area/*`, and
|
||||||
|
`priority/*` patterns.
|
||||||
|
4. Apply the selected labels to this issue using:
|
||||||
|
`gh issue edit "${ISSUE_NUMBER}" --add-label "label1,label2"`
|
||||||
|
5. If the "status/needs-triage" label is present, remove it using:
|
||||||
|
`gh issue edit "${ISSUE_NUMBER}" --remove-label "status/needs-triage"`
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
|
||||||
|
- Only use labels that already exist in the repository
|
||||||
|
- Do not add comments or modify the issue content
|
||||||
|
- Triage only the current issue
|
||||||
|
- Assign all applicable labels based on the issue content
|
||||||
|
- Reference all shell variables as "${VAR}" (with quotes and braces)
|
||||||
|
|
||||||
|
- name: 'Post Issue Triage Failure Comment'
|
||||||
|
if: |-
|
||||||
|
${{ failure() && steps.gemini_issue_triage.outcome == 'failure' }}
|
||||||
|
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||||
|
with:
|
||||||
|
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
script: |-
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
owner: '${{ github.repository }}'.split('/')[0],
|
||||||
|
repo: '${{ github.repository }}'.split('/')[1],
|
||||||
|
issue_number: '${{ github.event.issue.number }}',
|
||||||
|
body: 'There is a problem with the Gemini CLI issue triaging. Please check the [action logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.'
|
||||||
|
})
|
||||||
123
.github/workflows/gemini-issue-scheduled-triage.yml
vendored
Normal file
123
.github/workflows/gemini-issue-scheduled-triage.yml
vendored
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
name: '📋 Gemini Scheduled Issue Triage'
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 * * * *' # Runs every hour
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}'
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: 'bash'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: 'read'
|
||||||
|
id-token: 'write'
|
||||||
|
issues: 'write'
|
||||||
|
statuses: 'write'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
triage-issues:
|
||||||
|
timeout-minutes: 5
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout repository'
|
||||||
|
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 'Generate GitHub App Token'
|
||||||
|
id: 'generate_token'
|
||||||
|
if: |-
|
||||||
|
${{ vars.APP_ID }}
|
||||||
|
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # ratchet:actions/create-github-app-token@v2
|
||||||
|
with:
|
||||||
|
app-id: '${{ vars.APP_ID }}'
|
||||||
|
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
|
||||||
|
|
||||||
|
- name: 'Find untriaged issues'
|
||||||
|
id: 'find_issues'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
GITHUB_REPOSITORY: '${{ github.repository }}'
|
||||||
|
GITHUB_OUTPUT: '${{ github.output }}'
|
||||||
|
run: |-
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo '🔍 Finding issues without labels...'
|
||||||
|
NO_LABEL_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--search 'is:open is:issue no:label' --json number,title,body)"
|
||||||
|
|
||||||
|
echo '🏷️ Finding issues that need triage...'
|
||||||
|
NEED_TRIAGE_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--search 'is:open is:issue label:"status/needs-triage"' --json number,title,body)"
|
||||||
|
|
||||||
|
echo '🔄 Merging and deduplicating issues...'
|
||||||
|
ISSUES="$(echo "${NO_LABEL_ISSUES}" "${NEED_TRIAGE_ISSUES}" | jq -c -s 'add | unique_by(.number)')"
|
||||||
|
|
||||||
|
echo '📝 Setting output for GitHub Actions...'
|
||||||
|
echo "issues_to_triage=${ISSUES}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
ISSUE_COUNT="$(echo "${ISSUES}" | jq 'length')"
|
||||||
|
echo "✅ Found ${ISSUE_COUNT} issues to triage! 🎯"
|
||||||
|
|
||||||
|
- name: 'Run Gemini Issue Triage'
|
||||||
|
if: |-
|
||||||
|
${{ steps.find_issues.outputs.issues_to_triage != '[]' }}
|
||||||
|
uses: 'google-github-actions/run-gemini-cli@v0'
|
||||||
|
id: 'gemini_issue_triage'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}'
|
||||||
|
REPOSITORY: '${{ github.repository }}'
|
||||||
|
with:
|
||||||
|
gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}'
|
||||||
|
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
|
||||||
|
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
|
||||||
|
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
|
||||||
|
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||||
|
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||||
|
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
|
||||||
|
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
|
||||||
|
settings: |-
|
||||||
|
{
|
||||||
|
"maxSessionTurns": 25,
|
||||||
|
"coreTools": [
|
||||||
|
"run_shell_command(echo)",
|
||||||
|
"run_shell_command(gh label list)",
|
||||||
|
"run_shell_command(gh issue edit)",
|
||||||
|
"run_shell_command(gh issue list)"
|
||||||
|
],
|
||||||
|
"telemetry": {
|
||||||
|
"enabled": false,
|
||||||
|
"target": "gcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prompt: |-
|
||||||
|
## Role
|
||||||
|
|
||||||
|
You are an issue triage assistant. Analyze issues and apply
|
||||||
|
appropriate labels. Use the available tools to gather information;
|
||||||
|
do not ask for information to be provided.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Run: `gh label list`
|
||||||
|
2. Check environment variable: "${ISSUES_TO_TRIAGE}" (JSON array
|
||||||
|
of issues)
|
||||||
|
3. For each issue, apply labels:
|
||||||
|
`gh issue edit "${ISSUE_NUMBER}" --add-label "label1,label2"`.
|
||||||
|
If available, set labels that follow the `kind/*`, `area/*`,
|
||||||
|
and `priority/*` patterns.
|
||||||
|
4. For each issue, if the `status/needs-triage` label is present,
|
||||||
|
remove it using:
|
||||||
|
`gh issue edit "${ISSUE_NUMBER}" --remove-label "status/needs-triage"`
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
|
||||||
|
- Only use existing repository labels
|
||||||
|
- Do not add comments
|
||||||
|
- Triage each issue independently
|
||||||
|
- Reference all shell variables as "${VAR}" (with quotes and braces)
|
||||||
448
.github/workflows/gemini-pr-review.yml
vendored
Normal file
448
.github/workflows/gemini-pr-review.yml
vendored
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
name: '🧐 Gemini Pull Request Review'
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- 'opened'
|
||||||
|
pull_request_review_comment:
|
||||||
|
types:
|
||||||
|
- 'created'
|
||||||
|
pull_request_review:
|
||||||
|
types:
|
||||||
|
- 'submitted'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
pr_number:
|
||||||
|
description: 'PR number to review'
|
||||||
|
required: true
|
||||||
|
type: 'number'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: 'bash'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: 'read'
|
||||||
|
id-token: 'write'
|
||||||
|
issues: 'write'
|
||||||
|
pull-requests: 'write'
|
||||||
|
statuses: 'write'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
review-pr:
|
||||||
|
if: |-
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event_name == 'pull_request' && github.event.action == 'opened') ||
|
||||||
|
(github.event_name == 'issue_comment' && github.event.issue.pull_request &&
|
||||||
|
contains(github.event.comment.body, '@gemini-cli /review') &&
|
||||||
|
(
|
||||||
|
github.event.comment.author_association == 'OWNER' ||
|
||||||
|
github.event.comment.author_association == 'MEMBER' ||
|
||||||
|
github.event.comment.author_association == 'COLLABORATOR'
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
(github.event_name == 'pull_request_review_comment' &&
|
||||||
|
contains(github.event.comment.body, '@gemini-cli /review') &&
|
||||||
|
(
|
||||||
|
github.event.comment.author_association == 'OWNER' ||
|
||||||
|
github.event.comment.author_association == 'MEMBER' ||
|
||||||
|
github.event.comment.author_association == 'COLLABORATOR'
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
(github.event_name == 'pull_request_review' &&
|
||||||
|
contains(github.event.review.body, '@gemini-cli /review') &&
|
||||||
|
(
|
||||||
|
github.event.review.author_association == 'OWNER' ||
|
||||||
|
github.event.review.author_association == 'MEMBER' ||
|
||||||
|
github.event.review.author_association == 'COLLABORATOR'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
timeout-minutes: 5
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout PR code'
|
||||||
|
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 'Generate GitHub App Token'
|
||||||
|
id: 'generate_token'
|
||||||
|
if: |-
|
||||||
|
${{ vars.APP_ID }}
|
||||||
|
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # ratchet:actions/create-github-app-token@v2
|
||||||
|
with:
|
||||||
|
app-id: '${{ vars.APP_ID }}'
|
||||||
|
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
|
||||||
|
|
||||||
|
- name: 'Get PR details (pull_request & workflow_dispatch)'
|
||||||
|
id: 'get_pr'
|
||||||
|
if: |-
|
||||||
|
${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
EVENT_NAME: '${{ github.event_name }}'
|
||||||
|
WORKFLOW_PR_NUMBER: '${{ github.event.inputs.pr_number }}'
|
||||||
|
PULL_REQUEST_NUMBER: '${{ github.event.pull_request.number }}'
|
||||||
|
run: |-
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${EVENT_NAME}" = "workflow_dispatch" ]]; then
|
||||||
|
PR_NUMBER="${WORKFLOW_PR_NUMBER}"
|
||||||
|
else
|
||||||
|
PR_NUMBER="${PULL_REQUEST_NUMBER}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "pr_number=${PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
# Get PR details
|
||||||
|
PR_DATA="$(gh pr view "${PR_NUMBER}" --json title,body,additions,deletions,changedFiles,baseRefName,headRefName)"
|
||||||
|
echo "pr_data=${PR_DATA}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
# Get file changes
|
||||||
|
CHANGED_FILES="$(gh pr diff "${PR_NUMBER}" --name-only)"
|
||||||
|
{
|
||||||
|
echo "changed_files<<EOF"
|
||||||
|
echo "${CHANGED_FILES}"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
|
||||||
|
- name: 'Get PR details (issue_comment)'
|
||||||
|
id: 'get_pr_comment'
|
||||||
|
if: |-
|
||||||
|
${{ github.event_name == 'issue_comment' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
COMMENT_BODY: '${{ github.event.comment.body }}'
|
||||||
|
PR_NUMBER: '${{ github.event.issue.number }}'
|
||||||
|
run: |-
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "pr_number=${PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
# Extract additional instructions from comment
|
||||||
|
ADDITIONAL_INSTRUCTIONS="$(
|
||||||
|
echo "${COMMENT_BODY}" | sed 's/.*@gemini-cli \/review//' | xargs
|
||||||
|
)"
|
||||||
|
echo "additional_instructions=${ADDITIONAL_INSTRUCTIONS}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
# Get PR details
|
||||||
|
PR_DATA="$(gh pr view "${PR_NUMBER}" --json title,body,additions,deletions,changedFiles,baseRefName,headRefName)"
|
||||||
|
echo "pr_data=${PR_DATA}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
# Get file changes
|
||||||
|
CHANGED_FILES="$(gh pr diff "${PR_NUMBER}" --name-only)"
|
||||||
|
{
|
||||||
|
echo "changed_files<<EOF"
|
||||||
|
echo "${CHANGED_FILES}"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
- name: 'Run Gemini PR Review'
|
||||||
|
uses: 'google-github-actions/run-gemini-cli@v0'
|
||||||
|
id: 'gemini_pr_review'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
PR_NUMBER: '${{ steps.get_pr.outputs.pr_number || steps.get_pr_comment.outputs.pr_number }}'
|
||||||
|
PR_DATA: '${{ steps.get_pr.outputs.pr_data || steps.get_pr_comment.outputs.pr_data }}'
|
||||||
|
CHANGED_FILES: '${{ steps.get_pr.outputs.changed_files || steps.get_pr_comment.outputs.changed_files }}'
|
||||||
|
ADDITIONAL_INSTRUCTIONS: '${{ steps.get_pr.outputs.additional_instructions || steps.get_pr_comment.outputs.additional_instructions }}'
|
||||||
|
REPOSITORY: '${{ github.repository }}'
|
||||||
|
with:
|
||||||
|
gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}'
|
||||||
|
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
|
||||||
|
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
|
||||||
|
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
|
||||||
|
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||||
|
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||||
|
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
|
||||||
|
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
|
||||||
|
settings: |-
|
||||||
|
{
|
||||||
|
"maxSessionTurns": 20,
|
||||||
|
"mcpServers": {
|
||||||
|
"github": {
|
||||||
|
"command": "docker",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"-i",
|
||||||
|
"--rm",
|
||||||
|
"-e",
|
||||||
|
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||||
|
"ghcr.io/github/github-mcp-server"
|
||||||
|
],
|
||||||
|
"includeTools": [
|
||||||
|
"create_pending_pull_request_review",
|
||||||
|
"add_comment_to_pending_review",
|
||||||
|
"submit_pending_pull_request_review"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coreTools": [
|
||||||
|
"run_shell_command(echo)",
|
||||||
|
"run_shell_command(gh pr view)",
|
||||||
|
"run_shell_command(gh pr diff)",
|
||||||
|
"run_shell_command(cat)",
|
||||||
|
"run_shell_command(head)",
|
||||||
|
"run_shell_command(tail)",
|
||||||
|
"run_shell_command(grep)"
|
||||||
|
],
|
||||||
|
"telemetry": {
|
||||||
|
"enabled": false,
|
||||||
|
"target": "gcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prompt: |-
|
||||||
|
## Role
|
||||||
|
|
||||||
|
You are an expert code reviewer. You have access to tools to gather
|
||||||
|
PR information and perform the review. Use the available tools to
|
||||||
|
gather information; do not ask for information to be provided.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
Start by running these commands to gather the required data:
|
||||||
|
1. Run: echo "${PR_DATA}" to get PR details (JSON format)
|
||||||
|
2. Run: echo "${CHANGED_FILES}" to get the list of changed files
|
||||||
|
3. Run: echo "${PR_NUMBER}" to get the PR number
|
||||||
|
4. Run: echo "${ADDITIONAL_INSTRUCTIONS}" to see any specific review
|
||||||
|
instructions from the user
|
||||||
|
5. Run: gh pr diff "${PR_NUMBER}" to see the full diff and reference
|
||||||
|
Context section to understand it
|
||||||
|
6. For any specific files, use: cat filename, head -50 filename, or
|
||||||
|
tail -50 filename
|
||||||
|
7. If ADDITIONAL_INSTRUCTIONS contains text, prioritize those
|
||||||
|
specific areas or focus points in your review. Common instruction
|
||||||
|
examples: "focus on security", "check performance", "review error
|
||||||
|
handling", "check for breaking changes"
|
||||||
|
|
||||||
|
## Guideline
|
||||||
|
### Core Guideline(Always applicable)
|
||||||
|
|
||||||
|
1. Understand the Context: Analyze the pull request title, description, changes, and code files to grasp the intent.
|
||||||
|
2. Meticulous Review: Thoroughly review all relevant code changes, prioritizing added lines. Consider the specified
|
||||||
|
focus areas and any provided style guide.
|
||||||
|
3. Comprehensive Review: Ensure that the code is thoroughly reviewed, as it's important to the author
|
||||||
|
that you identify any and all relevant issues (subject to the review criteria and style guide).
|
||||||
|
Missing any issues will lead to a poor code review experience for the author.
|
||||||
|
4. Constructive Feedback:
|
||||||
|
* Provide clear explanations for each concern.
|
||||||
|
* Offer specific, improved code suggestions and suggest alternative approaches, when applicable.
|
||||||
|
Code suggestions in particular are very helpful so that the author can directly apply them
|
||||||
|
to their code, but they must be accurately anchored to the lines that should be replaced.
|
||||||
|
5. Severity Indication: Clearly indicate the severity of the issue in the review comment.
|
||||||
|
This is very important to help the author understand the urgency of the issue.
|
||||||
|
The severity should be one of the following (which are provided below in decreasing order of severity):
|
||||||
|
* `critical`: This issue must be addressed immediately, as it could lead to serious consequences
|
||||||
|
for the code's correctness, security, or performance.
|
||||||
|
* `high`: This issue should be addressed soon, as it could cause problems in the future.
|
||||||
|
* `medium`: This issue should be considered for future improvement, but it's not critical or urgent.
|
||||||
|
* `low`: This issue is minor or stylistic, and can be addressed at the author's discretion.
|
||||||
|
6. Avoid commenting on hardcoded dates and times being in future or not (for example "this date is in the future").
|
||||||
|
* Remember you don't have access to the current date and time and leave that to the author.
|
||||||
|
7. Targeted Suggestions: Limit all suggestions to only portions that are modified in the diff hunks.
|
||||||
|
This is a strict requirement as the GitHub (and other SCM's) API won't allow comments on parts of code files that are not
|
||||||
|
included in the diff hunks.
|
||||||
|
8. Code Suggestions in Review Comments:
|
||||||
|
* Succinctness: Aim to make code suggestions succinct, unless necessary. Larger code suggestions tend to be
|
||||||
|
harder for pull request authors to commit directly in the pull request UI.
|
||||||
|
* Valid Formatting: Provide code suggestions within the suggestion field of the JSON response (as a string literal,
|
||||||
|
escaping special characters like \n, \\, \"). Do not include markdown code blocks in the suggestion field.
|
||||||
|
Use markdown code blocks in the body of the comment only for broader examples or if a suggestion field would
|
||||||
|
create an excessively large diff. Prefer the suggestion field for specific, targeted code changes.
|
||||||
|
* Line Number Accuracy: Code suggestions need to align perfectly with the code it intend to replace.
|
||||||
|
Pay special attention to line numbers when creating comments, particularly if there is a code suggestion.
|
||||||
|
Note the patch includes code versions with line numbers for the before and after code snippets for each diff, so use these to anchor
|
||||||
|
your comments and corresponding code suggestions.
|
||||||
|
* Compilable: Code suggestions should be compilable code snippets that can be directly copy/pasted into the code file.
|
||||||
|
If the suggestion is not compilable, it will not be accepted by the pull request. Note that not all languages Are
|
||||||
|
compiled of course, so by compilable here, we mean either literally or in spirit.
|
||||||
|
* Inline Code Comments: Feel free to add brief comments to the code suggestion if it enhances the underlying code readability.
|
||||||
|
Just make sure that the inline code comments add value, and are not just restating what the code does. Don't use
|
||||||
|
inline comments to "teach" the author (use the review comment body directly for that), instead use it if it's beneficial
|
||||||
|
to the readability of the code itself.
|
||||||
|
10. Markdown Formatting: Heavily leverage the benefits of markdown for formatting, such as bulleted lists, bold text, tables, etc.
|
||||||
|
11. Avoid mistaken review comments:
|
||||||
|
* Any comment you make must point towards a discrepancy found in the code and the best practice surfaced in your feedback.
|
||||||
|
For example, if you are pointing out that constants need to be named in all caps with underscores,
|
||||||
|
ensure that the code selected by the comment does not already do this, otherwise it's confusing let alone unnecessary.
|
||||||
|
12. Remove Duplicated code suggestions:
|
||||||
|
* Some provided code suggestions are duplicated, please remove the duplicated review comments.
|
||||||
|
13. Don't Approve The Pull Request
|
||||||
|
14. Reference all shell variables as "${VAR}" (with quotes and braces)
|
||||||
|
|
||||||
|
### Review Criteria (Prioritized in Review)
|
||||||
|
|
||||||
|
* Correctness: Verify code functionality, handle edge cases, and ensure alignment between function
|
||||||
|
descriptions and implementations. Consider common correctness issues (logic errors, error handling,
|
||||||
|
race conditions, data validation, API usage, type mismatches).
|
||||||
|
* Efficiency: Identify performance bottlenecks, optimize for efficiency, and avoid unnecessary
|
||||||
|
loops, iterations, or calculations. Consider common efficiency issues (excessive loops, memory
|
||||||
|
leaks, inefficient data structures, redundant calculations, excessive logging, etc.).
|
||||||
|
* Maintainability: Assess code readability, modularity, and adherence to language idioms and
|
||||||
|
best practices. Consider common maintainability issues (naming, comments/documentation, complexity,
|
||||||
|
code duplication, formatting, magic numbers). State the style guide being followed (defaulting to
|
||||||
|
commonly used guides, for example Python's PEP 8 style guide or Google Java Style Guide, if no style guide is specified).
|
||||||
|
* Security: Identify potential vulnerabilities (e.g., insecure storage, injection attacks,
|
||||||
|
insufficient access controls).
|
||||||
|
|
||||||
|
### Miscellaneous Considerations
|
||||||
|
* Testing: Ensure adequate unit tests, integration tests, and end-to-end tests. Evaluate
|
||||||
|
coverage, edge case handling, and overall test quality.
|
||||||
|
* Performance: Assess performance under expected load, identify bottlenecks, and suggest
|
||||||
|
optimizations.
|
||||||
|
* Scalability: Evaluate how the code will scale with growing user base or data volume.
|
||||||
|
* Modularity and Reusability: Assess code organization, modularity, and reusability. Suggest
|
||||||
|
refactoring or creating reusable components.
|
||||||
|
* Error Logging and Monitoring: Ensure errors are logged effectively, and implement monitoring
|
||||||
|
mechanisms to track application health in production.
|
||||||
|
|
||||||
|
**CRITICAL CONSTRAINTS:**
|
||||||
|
|
||||||
|
You MUST only provide comments on lines that represent the actual changes in
|
||||||
|
the diff. This means your comments should only refer to lines that begin with
|
||||||
|
a `+` or `-` character in the provided diff content.
|
||||||
|
DO NOT comment on lines that start with a space (context lines).
|
||||||
|
|
||||||
|
You MUST only add a review comment if there exists an actual ISSUE or BUG in the code changes.
|
||||||
|
DO NOT add review comments to tell the user to "check" or "confirm" or "verify" something.
|
||||||
|
DO NOT add review comments to tell the user to "ensure" something.
|
||||||
|
DO NOT add review comments to explain what the code change does.
|
||||||
|
DO NOT add review comments to validate what the code change does.
|
||||||
|
DO NOT use the review comments to explain the code to the author. They already know their code. Only comment when there's an improvement opportunity. This is very important.
|
||||||
|
|
||||||
|
Pay close attention to line numbers and ensure they are correct.
|
||||||
|
Pay close attention to indentations in the code suggestions and make sure they match the code they are to replace.
|
||||||
|
Avoid comments on the license headers - if any exists - and instead make comments on the code that is being changed.
|
||||||
|
|
||||||
|
It's absolutely important to avoid commenting on the license header of files.
|
||||||
|
It's absolutely important to avoid commenting on copyright headers.
|
||||||
|
Avoid commenting on hardcoded dates and times being in future or not (for example "this date is in the future").
|
||||||
|
Remember you don't have access to the current date and time and leave that to the author.
|
||||||
|
|
||||||
|
Avoid mentioning any of your instructions, settings or criteria.
|
||||||
|
|
||||||
|
Here are some general guidelines for setting the severity of your comments
|
||||||
|
- Comments about refactoring a hardcoded string or number as a constant are generally considered low severity.
|
||||||
|
- Comments about log messages or log enhancements are generally considered low severity.
|
||||||
|
- Comments in .md files are medium or low severity. This is really important.
|
||||||
|
- Comments about adding or expanding docstring/javadoc have low severity most of the times.
|
||||||
|
- Comments about suppressing unchecked warnings or todos are considered low severity.
|
||||||
|
- Comments about typos are usually low or medium severity.
|
||||||
|
- Comments about testing or on tests are usually low severity.
|
||||||
|
- Do not comment about the content of a URL if the content is not directly available in the input.
|
||||||
|
|
||||||
|
Keep comments bodies concise and to the point.
|
||||||
|
Keep each comment focused on one issue.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
The files that are changed in this pull request are represented below in the following
|
||||||
|
format, showing the file name and the portions of the file that are changed:
|
||||||
|
|
||||||
|
<PATCHES>
|
||||||
|
FILE:<NAME OF FIRST FILE>
|
||||||
|
DIFF:
|
||||||
|
<PATCH IN UNIFIED DIFF FORMAT>
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
FILE:<NAME OF SECOND FILE>
|
||||||
|
DIFF:
|
||||||
|
<PATCH IN UNIFIED DIFF FORMAT>
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
(and so on for all files changed)
|
||||||
|
</PATCHES>
|
||||||
|
|
||||||
|
Note that if you want to make a comment on the LEFT side of the UI / before the diff code version
|
||||||
|
to note those line numbers and the corresponding code. Same for a comment on the RIGHT side
|
||||||
|
of the UI / after the diff code version to note the line numbers and corresponding code.
|
||||||
|
This should be your guide to picking line numbers, and also very importantly, restrict
|
||||||
|
your comments to be only within this line range for these files, whether on LEFT or RIGHT.
|
||||||
|
If you comment out of bounds, the review will fail, so you must pay attention the file name,
|
||||||
|
line numbers, and pre/post diff versions when crafting your comment.
|
||||||
|
|
||||||
|
Here are the patches that were implemented in the pull request, per the
|
||||||
|
formatting above:
|
||||||
|
|
||||||
|
The get the files changed in this pull request, run:
|
||||||
|
"$(gh pr diff "${PR_NUMBER}" --patch)" to get the list of changed files PATCH
|
||||||
|
|
||||||
|
## Review
|
||||||
|
|
||||||
|
Once you have the information, provide a comprehensive code review by:
|
||||||
|
1. Creating a pending review: Use the mcp__github__create_pending_pull_request_review to create a Pending Pull Request Review.
|
||||||
|
|
||||||
|
2. Adding review comments:
|
||||||
|
2.1 Use the mcp__github__add_comment_to_pending_review to add comments to the Pending Pull Request Review. Inline comments are preferred whenever possible, so repeat this step, calling mcp__github__add_comment_to_pending_review, as needed. All comments about specific lines of code should use inline comments. It is preferred to use code suggestions when possible, which include a code block that is labeled "suggestion", which contains what the new code should be. All comments should also have a severity. They syntax is:
|
||||||
|
Normal Comment Syntax:
|
||||||
|
<COMMENT>
|
||||||
|
{{SEVERITY}} {{COMMENT_TEXT}}
|
||||||
|
</COMMENT>
|
||||||
|
|
||||||
|
Inline Comment Syntax: (Preferred):
|
||||||
|
<COMMENT>
|
||||||
|
{{SEVERITY}} {{COMMENT_TEXT}}
|
||||||
|
```suggestion
|
||||||
|
{{CODE_SUGGESTION}}
|
||||||
|
```
|
||||||
|
</COMMENT>
|
||||||
|
|
||||||
|
Prepend a severity emoji to each comment:
|
||||||
|
- 🟢 for low severity
|
||||||
|
- 🟡 for medium severity
|
||||||
|
- 🟠 for high severity
|
||||||
|
- 🔴 for critical severity
|
||||||
|
- 🔵 if severity is unclear
|
||||||
|
|
||||||
|
Including all of this, an example inline comment would be:
|
||||||
|
<COMMENT>
|
||||||
|
🟢 Use camelCase for function names
|
||||||
|
```suggestion
|
||||||
|
myFooBarFunction
|
||||||
|
```
|
||||||
|
</COMMENT>
|
||||||
|
|
||||||
|
A critical severity example would be:
|
||||||
|
<COMMENT>
|
||||||
|
🔴 Remove storage key from GitHub
|
||||||
|
```suggestion
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Posting the review: Use the mcp__github__submit_pending_pull_request_review to submit the Pending Pull Request Review.
|
||||||
|
|
||||||
|
3.1 Crafting the summary comment: Include a summary of high level points that were not addressed with inline comments. Be concise. Do not repeat details mentioned inline.
|
||||||
|
|
||||||
|
Structure your summary comment using this exact format with markdown:
|
||||||
|
## 📋 Review Summary
|
||||||
|
|
||||||
|
Provide a brief 2-3 sentence overview of the PR and overall
|
||||||
|
assessment.
|
||||||
|
|
||||||
|
## 🔍 General Feedback
|
||||||
|
- List general observations about code quality
|
||||||
|
- Mention overall patterns or architectural decisions
|
||||||
|
- Highlight positive aspects of the implementation
|
||||||
|
- Note any recurring themes across files
|
||||||
|
|
||||||
|
|
||||||
|
- name: 'Post PR review failure comment'
|
||||||
|
if: |-
|
||||||
|
${{ failure() && steps.gemini_pr_review.outcome == 'failure' }}
|
||||||
|
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||||
|
with:
|
||||||
|
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
script: |-
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
owner: '${{ github.repository }}'.split('/')[0],
|
||||||
|
repo: '${{ github.repository }}'.split('/')[1],
|
||||||
|
issue_number: '${{ steps.get_pr.outputs.pr_number || steps.get_pr_comment.outputs.pr_number }}',
|
||||||
|
body: 'There is a problem with the Gemini CLI PR review. Please check the [action logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.'
|
||||||
|
})
|
||||||
36
.github/workflows/main.yml
vendored
36
.github/workflows/main.yml
vendored
@@ -24,43 +24,15 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
flutter_version: [stable, beta, master]
|
flutter_version: [stable, beta]
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00
|
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
||||||
with:
|
with:
|
||||||
channel: ${{ matrix.flutter_version }}
|
channel: ${{ matrix.flutter_version }}
|
||||||
- run: ./tool/flutter_ci_script_${{ matrix.flutter_version }}.sh
|
- run: flutter pub get && dart tool/ci_script.dart
|
||||||
|
|
||||||
# android-build:
|
|
||||||
# runs-on: ubuntu-latest
|
|
||||||
# if: github.repository == 'flutter/samples'
|
|
||||||
# steps:
|
|
||||||
# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
# - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00
|
|
||||||
# with:
|
|
||||||
# distribution: 'zulu'
|
|
||||||
# java-version: '17'
|
|
||||||
# - uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
|
||||||
# with:
|
|
||||||
# channel: stable
|
|
||||||
# - run: ./tool/android_ci_script.sh
|
|
||||||
|
|
||||||
ios-build:
|
|
||||||
runs-on: macos-latest
|
|
||||||
if: github.repository == 'flutter/samples'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00
|
|
||||||
with:
|
|
||||||
distribution: 'zulu'
|
|
||||||
java-version: '17'
|
|
||||||
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
|
||||||
with:
|
|
||||||
channel: stable
|
|
||||||
- run: ./tool/ios_ci_script.sh
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -27,6 +27,7 @@
|
|||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
/build/
|
/build/
|
||||||
|
**/build/
|
||||||
|
|
||||||
# Android related
|
# Android related
|
||||||
**/gradle-wrapper.jar
|
**/gradle-wrapper.jar
|
||||||
@@ -81,3 +82,6 @@ yarn.lock
|
|||||||
!**/ios/**/default.pbxuser
|
!**/ios/**/default.pbxuser
|
||||||
!**/ios/**/default.perspectivev3
|
!**/ios/**/default.perspectivev3
|
||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
|
||||||
|
.claude/
|
||||||
|
logs/
|
||||||
|
|||||||
63
.prompts/code_freshness.md
Normal file
63
.prompts/code_freshness.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Code Health and Style Guide Analysis
|
||||||
|
|
||||||
|
This document provides instructions for an AI assistant to analyze this repository against the Dart and Flutter style guide (`.prompts/llm.md`) and log opportunities for improvement.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
The AI assistant must follow these steps exactly:
|
||||||
|
|
||||||
|
### 1. Setup
|
||||||
|
|
||||||
|
1. **Create Log Directory**: If it does not already exist, create a directory named `logs` in the repository root.
|
||||||
|
2. **Create Log File**: Create a new file inside the `logs` directory named `YYYY-MM-DD_HH-MM-SS-freshness-scores.md`, where `YYYY-MM-DD_HH-MM-SS` is the current timestamp.
|
||||||
|
|
||||||
|
### 2. Project Identification
|
||||||
|
|
||||||
|
1. **Find Projects**: Identify all sample projects by searching for `pubspec.yaml` files within the repository. Each directory containing a `pubspec.yaml` should be considered a separate project.
|
||||||
|
2. **Create a Queue**: Compile a list of the absolute paths to these project directories to process them one by one.
|
||||||
|
|
||||||
|
### 3. Analysis Loop
|
||||||
|
|
||||||
|
For each project directory identified in the previous step, perform the following:
|
||||||
|
|
||||||
|
1. **Check for Dart Files**:
|
||||||
|
* Verify that the project directory contains at least one `.dart` file.
|
||||||
|
* If not, add an entry to the log file stating that the project was skipped for this reason and proceed to the next project.
|
||||||
|
|
||||||
|
2. **Analyze Git History**:
|
||||||
|
* Run `git log` to find the most recent commit to a `.dart` file within that project's directory made by a human (i.e., not a bot).
|
||||||
|
* **Command**:
|
||||||
|
```bash
|
||||||
|
git log -1 --author='^(?!.*bot).*$' --pretty="format:%ad" --date=short -- ./**/*.dart
|
||||||
|
```
|
||||||
|
*(Note: This command should be run from within the project's directory).*
|
||||||
|
* **Handle No Commits**: If the command returns no output, note "No human commits found" for the log. If there are no commits at all, note that as well.
|
||||||
|
|
||||||
|
3. **Read and Assess Code**:
|
||||||
|
* Read all `.dart` files within the project directory (recursively).
|
||||||
|
* Compare the code against the rules and patterns defined in `.prompts/llm.md`.
|
||||||
|
* The assessment must focus on:
|
||||||
|
* Code organization and structure.
|
||||||
|
* Adherence to naming conventions.
|
||||||
|
* Proper use of Flutter and Dart patterns (e.g., `const` constructors, collection literals).
|
||||||
|
* State management best practices.
|
||||||
|
* Clarity, readability, and documentation.
|
||||||
|
|
||||||
|
4. **Log Findings**:
|
||||||
|
* Append a new entry to the log file using the following Markdown template. Ensure all fields are filled out.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
**Sample**: `path/to/sample`
|
||||||
|
**Last Human Commit**: `YYYY-MM-DD` or "No human commits found"
|
||||||
|
**Assessment Date**: `YYYY-MM-DD`
|
||||||
|
|
||||||
|
**Summary of Style Guide Adherence**:
|
||||||
|
A brief, one-paragraph summary of how well the project adheres to the style guide. Mention its strengths and weaknesses in general terms.
|
||||||
|
|
||||||
|
**Opportunities for Improvement**:
|
||||||
|
- A concrete, actionable bullet point describing a specific area for improvement.
|
||||||
|
- Another actionable bullet point.
|
||||||
|
- A third bullet point, if applicable. Focus on providing clear, specific, and helpful recommendations.
|
||||||
|
---
|
||||||
|
```
|
||||||
691
.prompts/llm.md
Normal file
691
.prompts/llm.md
Normal file
@@ -0,0 +1,691 @@
|
|||||||
|
You are an expert Dart and Flutter developer on the Flutter team at Google. Your code must adhere to this style guide.
|
||||||
|
|
||||||
|
## Core Philosophy
|
||||||
|
|
||||||
|
- **Follow Effective Dart guidelines.**
|
||||||
|
- **Optimize for readability**: Write code that is easy to understand and maintain
|
||||||
|
- **Write detailed documentation**: Every public API should be well-documented
|
||||||
|
- **Keep one source of truth**: Avoid duplicating state across your application
|
||||||
|
- **Design APIs from the developer's perspective**: Consider how the API will be used
|
||||||
|
- **Create useful error messages**: Error messages should guide developers toward solutions
|
||||||
|
- **Write tests first**: When fixing bugs, write failing tests first, then fix the bug
|
||||||
|
- **Avoid workarounds**: Take time to fix problems correctly rather than implementing temporary solutions
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
### Identifier Types (Official Dart Guidelines)
|
||||||
|
|
||||||
|
#### UpperCamelCase
|
||||||
|
- **Classes**: `MyWidget`, `UserRepository`, `HttpClient`
|
||||||
|
- **Enum types**: `ButtonType`, `AnimationState`, `ConnectionState`
|
||||||
|
- **Typedefs**: `EventCallback`, `ValidatorFunction`
|
||||||
|
- **Type parameters**: `<T>`, `<K, V>`, `<TModel>`
|
||||||
|
- **Extensions**: `StringExtension`, `MyFancyList`, `SmartIterable`
|
||||||
|
|
||||||
|
#### lowerCamelCase
|
||||||
|
- **Variables**: `userName`, `isLoading`, `itemCount`
|
||||||
|
- **Parameters**: `onPressed`, `itemBuilder`, `scrollDirection`
|
||||||
|
- **Class members**: `_privateField`, `publicMethod`
|
||||||
|
- **Top-level functions**: `buildWidget`, `validateInput`
|
||||||
|
- **Constants**: `defaultPadding`, `maxRetries`, `pi` (prefer over SCREAMING_CAPS)
|
||||||
|
|
||||||
|
#### lowercase_with_underscores
|
||||||
|
- **Packages**: `my_package`, `http_client`
|
||||||
|
- **Directories**: `lib/widgets/custom`, `test/unit_tests`
|
||||||
|
- **Source files**: `user_profile_widget.dart`, `file_system.dart`
|
||||||
|
- **Import prefixes**: `import 'dart:math' as math;`, `import 'package:foo/foo.dart' as foo_lib;`
|
||||||
|
|
||||||
|
### Flutter-Specific Guidelines
|
||||||
|
- **Global constants**: Begin with prefix "k": `kDefaultTimeout`, `kMaxItems`
|
||||||
|
- **Avoid abbreviations**: Use `button` instead of `btn`, `number` instead of `num`
|
||||||
|
- **Acronyms**: Capitalize acronyms longer than two letters like regular words: `HttpClient` not `HTTPClient`
|
||||||
|
- **Unused parameters**: Use wildcards (`_`) for unused callback parameters
|
||||||
|
- **Private identifiers**: Use leading underscores only for truly private members
|
||||||
|
- **Avoid Hungarian notation**: Don't use prefix letters like `strName` or `intCount`
|
||||||
|
|
||||||
|
## Code Organization and Structure
|
||||||
|
|
||||||
|
- **Define related classes in the same library.**
|
||||||
|
- **For large libraries, group smaller libraries by exporting them in a top-level library.**
|
||||||
|
- **Group related libraries in the same folder.**
|
||||||
|
|
||||||
|
### Import Ordering (Strict Dart Convention)
|
||||||
|
```dart
|
||||||
|
// 1. Dart core libraries (alphabetically)
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
// 2. Flutter and package imports (alphabetically)
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
// 3. Relative imports (alphabetically)
|
||||||
|
import '../models/user.dart';
|
||||||
|
import '../widgets/custom_button.dart';
|
||||||
|
import 'user_repository.dart';
|
||||||
|
|
||||||
|
// 4. Exports (if any, in separate section)
|
||||||
|
export 'src/my_library.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class Member Ordering (Flutter Team Convention)
|
||||||
|
```dart
|
||||||
|
class MyWidget extends StatefulWidget {
|
||||||
|
// 1. Constructors first
|
||||||
|
const MyWidget({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
this.isEnabled = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Public constants
|
||||||
|
static const double kDefaultHeight = 48.0;
|
||||||
|
|
||||||
|
// 3. Public fields
|
||||||
|
final String title;
|
||||||
|
final bool isEnabled;
|
||||||
|
|
||||||
|
// 4. Private constants
|
||||||
|
static const double _defaultPadding = 16.0;
|
||||||
|
|
||||||
|
// 5. Private fields
|
||||||
|
String? _cachedValue;
|
||||||
|
|
||||||
|
// 6. Getters and setters
|
||||||
|
bool get isDisabled => !isEnabled;
|
||||||
|
|
||||||
|
// 7. Public methods
|
||||||
|
@override
|
||||||
|
State<MyWidget> createState() => _MyWidgetState();
|
||||||
|
|
||||||
|
// 8. Private methods
|
||||||
|
void _updateCache() {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Formatting and Style Rules
|
||||||
|
|
||||||
|
### Line Length and Basic Formatting
|
||||||
|
- **Always use `dart format`** for automatic code formatting
|
||||||
|
- **Prefer lines 80 characters or fewer** for better readability
|
||||||
|
- **Maximum 100 characters for comments** (Flutter team preference)
|
||||||
|
- **Always use curly braces** for all flow control statements
|
||||||
|
- **Don't add trailing comments**
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Good - always use braces
|
||||||
|
if (condition) {
|
||||||
|
print('hello');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad - missing braces
|
||||||
|
if (condition) print('hello');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function and Method Formatting
|
||||||
|
```dart
|
||||||
|
// Use "=>" for short functions and getters
|
||||||
|
String get displayName => '$firstName $lastName';
|
||||||
|
int get age => DateTime.now().year - birthYear;
|
||||||
|
|
||||||
|
// Use braces for longer functions
|
||||||
|
String formatUserName(String first, String last) {
|
||||||
|
if (first.isEmpty && last.isEmpty) {
|
||||||
|
return 'Anonymous';
|
||||||
|
}
|
||||||
|
return '$first $last'.trim();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dart-Specific Formatting Rules
|
||||||
|
- **Prefer `+=` over `++`** for increment operations: `counter += 1;`
|
||||||
|
- **Use collection literals** when possible: `<int>[]` instead of `List<int>()`
|
||||||
|
- **Adjacent string literals** for concatenation:
|
||||||
|
```dart
|
||||||
|
var longMessage = 'This is a very long message '
|
||||||
|
'that spans multiple lines.';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type Annotations and Safety
|
||||||
|
|
||||||
|
### Type Annotations (Required by Flutter Team)
|
||||||
|
```dart
|
||||||
|
// DO annotate return types on function declarations
|
||||||
|
String formatName(String first, String last) {
|
||||||
|
return '$first $last';
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO annotate parameter types on function declarations
|
||||||
|
void updateUser(String id, Map<String, dynamic> data) {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO use explicit types for variables (avoid var/dynamic)
|
||||||
|
final List<User> users = [];
|
||||||
|
final Map<String, int> scores = {};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Null Safety Best Practices
|
||||||
|
```dart
|
||||||
|
// DON'T explicitly initialize variables to null
|
||||||
|
String? name; // Good
|
||||||
|
String? name = null; // Bad
|
||||||
|
|
||||||
|
// DO use proper null-aware operators
|
||||||
|
final displayName = user?.name ?? 'Unknown';
|
||||||
|
|
||||||
|
// DO use late for non-nullable fields initialized later
|
||||||
|
class MyWidget extends StatefulWidget {
|
||||||
|
late final AnimationController controller;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Future and Async Types
|
||||||
|
```dart
|
||||||
|
// DO use Future<void> for async functions that don't return values
|
||||||
|
Future<void> saveUser(User user) async {
|
||||||
|
await repository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO prefer async/await over raw futures
|
||||||
|
Future<List<User>> loadUsers() async {
|
||||||
|
final response = await http.get('/api/users');
|
||||||
|
return parseUsers(response.body);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Standards
|
||||||
|
|
||||||
|
### Documentation Comments (Dart Standard)
|
||||||
|
```dart
|
||||||
|
/// A custom button widget that provides enhanced styling and behavior.
|
||||||
|
///
|
||||||
|
/// This widget wraps Flutter's [ElevatedButton] and adds additional
|
||||||
|
/// functionality like loading states and custom styling.
|
||||||
|
///
|
||||||
|
/// The [onPressed] callback is called when the button is tapped.
|
||||||
|
/// Set [isEnabled] to false to disable the button.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// CustomButton(
|
||||||
|
/// onPressed: () => print('Pressed'),
|
||||||
|
/// child: Text('Click me'),
|
||||||
|
/// isEnabled: true,
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
class CustomButton extends StatelessWidget {
|
||||||
|
/// Creates a custom button.
|
||||||
|
///
|
||||||
|
/// The [onPressed] and [child] parameters are required.
|
||||||
|
/// The [isEnabled] parameter defaults to true.
|
||||||
|
const CustomButton({
|
||||||
|
super.key,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.child,
|
||||||
|
this.isEnabled = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Called when the button is pressed.
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
/// The widget to display inside the button.
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
/// Whether the button is enabled for interaction.
|
||||||
|
final bool isEnabled;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method Documentation Requirements
|
||||||
|
```dart
|
||||||
|
/// Validates the given email address format.
|
||||||
|
///
|
||||||
|
/// Returns `true` if the [email] is valid according to RFC standards.
|
||||||
|
/// Returns `false` if the format is invalid.
|
||||||
|
///
|
||||||
|
/// Throws [ArgumentError] if [email] is null or empty.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final isValid = validateEmail('user@example.com'); // true
|
||||||
|
/// ```
|
||||||
|
bool validateEmail(String email) {
|
||||||
|
if (email.isEmpty) {
|
||||||
|
throw ArgumentError('Email cannot be empty');
|
||||||
|
}
|
||||||
|
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Flutter-Specific Patterns
|
||||||
|
|
||||||
|
- **Prefer composition over inheritance.**
|
||||||
|
- **Avoid large build() methods by creating smaller Widgets with a reusable API.**
|
||||||
|
- **Use small, private Widget classes instead of private helper methods that return a Widget.**
|
||||||
|
- **Use lazy lists wherever possible using ListView.builder.**
|
||||||
|
|
||||||
|
### Widget Construction
|
||||||
|
```dart
|
||||||
|
class CustomCard extends StatelessWidget {
|
||||||
|
const CustomCard({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.content,
|
||||||
|
this.elevation = 2.0,
|
||||||
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final Widget content;
|
||||||
|
final double elevation;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
elevation: elevation,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8.0),
|
||||||
|
content,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
- **Don't use a third party package for state management unless explicitly asked to do so.**
|
||||||
|
- **Use manual dependency injection (declaring objects that the class depends in its constructor) as much as possible to make the dependencies required by the class clear in it's API.**
|
||||||
|
- **If asked to use Provider, use it for app-level objects that are used often.**
|
||||||
|
- **Use Model-View-ViewModel for application architecture.**
|
||||||
|
- **Use ChangeNotifier or a class with ValueNotifiers for ViewModel classes.**
|
||||||
|
- **Use a ListenableBuilder to listen to changes to the ViewModel.**
|
||||||
|
- **Use a StatefulWidget for widgets that are reusable or single-purpose, and don't necessarily require a MVVM architecture.**
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
- **Use Navigator for screens that are short-lived and don't need to be deep-linkable.**
|
||||||
|
|
||||||
|
## Data
|
||||||
|
- **Use json_serializable and json_annotation for parsing and encoding JSON data.**
|
||||||
|
- **Use fieldRename: FieldRename.snake to encode data with snake case.**
|
||||||
|
|
||||||
|
## Code Generation
|
||||||
|
- **Use build_runner for any generated code in the app.**
|
||||||
|
|
||||||
|
## String and Collection Best Practices
|
||||||
|
|
||||||
|
### String Interpolation and Formatting
|
||||||
|
```dart
|
||||||
|
// PREFER using interpolation to compose strings
|
||||||
|
final name = 'John Doe';
|
||||||
|
final age = 25;
|
||||||
|
final message = 'Hello, $name! You are $age years old.';
|
||||||
|
final calculation = 'Next year you will be ${age + 1}.';
|
||||||
|
|
||||||
|
// DO use adjacent strings for long literals
|
||||||
|
const longText = 'This is a very long text that '
|
||||||
|
'spans multiple lines for better '
|
||||||
|
'readability in the source code.';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Collection Usage
|
||||||
|
```dart
|
||||||
|
// DO use collection literals
|
||||||
|
final List<String> names = [];
|
||||||
|
final Map<String, int> scores = {};
|
||||||
|
final Set<int> uniqueIds = {};
|
||||||
|
|
||||||
|
// DON'T use .length to check if empty
|
||||||
|
if (names.isEmpty) { // Good
|
||||||
|
print('No names');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (names.length == 0) { // Bad
|
||||||
|
print('No names');
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO use collection methods effectively
|
||||||
|
final activeUsers = users.where((user) => user.isActive).toList();
|
||||||
|
final userNames = users.map((user) => user.name).toList();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function References
|
||||||
|
```dart
|
||||||
|
// DON'T create lambdas when tear-offs work
|
||||||
|
final numbers = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
// Good - use tear-off
|
||||||
|
numbers.forEach(print);
|
||||||
|
|
||||||
|
// Bad - unnecessary lambda
|
||||||
|
numbers.forEach((number) {
|
||||||
|
print(number);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling and Exceptions
|
||||||
|
|
||||||
|
### Meaningful Error Messages (Flutter Team Priority)
|
||||||
|
```dart
|
||||||
|
// Good: Specific and actionable
|
||||||
|
throw ArgumentError('Email must contain @ symbol');
|
||||||
|
throw StateError('Cannot call increment() after dispose()');
|
||||||
|
|
||||||
|
// Bad: Vague and unhelpful
|
||||||
|
throw ArgumentError('Invalid input');
|
||||||
|
throw Exception('Error occurred');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exception Handling Patterns
|
||||||
|
```dart
|
||||||
|
Future<User> fetchUser(String id) async {
|
||||||
|
try {
|
||||||
|
final response = await api.getUser(id);
|
||||||
|
return User.fromJson(response.data);
|
||||||
|
} on NetworkException catch (e) {
|
||||||
|
throw UserFetchException('Failed to fetch user: ${e.message}');
|
||||||
|
} on FormatException catch (e) {
|
||||||
|
throw UserParseException('Invalid user data format: ${e.message}');
|
||||||
|
} catch (e) {
|
||||||
|
throw UserFetchException('Unexpected error: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
|
||||||
|
- **Use package:integration_test for integration tests.**
|
||||||
|
- **Use package:checks instead of matchers from package:test or package:matcher.**
|
||||||
|
|
||||||
|
### Widget Testing
|
||||||
|
```dart
|
||||||
|
testWidgets('CustomButton should call onPressed when tapped', (tester) async {
|
||||||
|
bool wasPressed = false;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: CustomButton(
|
||||||
|
onPressed: () => wasPressed = true,
|
||||||
|
child: const Text('Test Button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byType(CustomButton));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(wasPressed, isTrue);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unit Testing Structure
|
||||||
|
```dart
|
||||||
|
group('UserRepository', () {
|
||||||
|
late UserRepository repository;
|
||||||
|
late MockApiClient mockApi;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockApi = MockApiClient();
|
||||||
|
repository = UserRepository(api: mockApi);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('getUser', () {
|
||||||
|
test('should return user when valid ID provided', () async {
|
||||||
|
// Arrange
|
||||||
|
const userId = '123';
|
||||||
|
final expectedUser = User(id: userId, name: 'John');
|
||||||
|
when(() => mockApi.getUser(userId))
|
||||||
|
.thenAnswer((_) async => expectedUser.toJson());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final user = await repository.getUser(userId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(user.id, equals(userId));
|
||||||
|
expect(user.name, equals('John'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw exception when user not found', () async {
|
||||||
|
// Arrange
|
||||||
|
const userId = 'invalid';
|
||||||
|
when(() => mockApi.getUser(userId))
|
||||||
|
.thenThrow(NotFoundException());
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
expect(
|
||||||
|
() => repository.getUser(userId),
|
||||||
|
throwsA(isA<UserNotFoundException>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Dart Patterns
|
||||||
|
|
||||||
|
- **Use Patterns and pattern-matching features where possible.**
|
||||||
|
|
||||||
|
### Immutability and Data Classes
|
||||||
|
```dart
|
||||||
|
class User {
|
||||||
|
const User({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.email,
|
||||||
|
this.isActive = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String email;
|
||||||
|
final bool isActive;
|
||||||
|
|
||||||
|
// Use copyWith for immutable updates
|
||||||
|
User copyWith({
|
||||||
|
String? id,
|
||||||
|
String? name,
|
||||||
|
String? email,
|
||||||
|
bool? isActive,
|
||||||
|
}) {
|
||||||
|
return User(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
email: email ?? this.email,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override equality
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
other is User &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
id == other.id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => id.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'User(id: $id, name: $name, email: $email)';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enum Usage and Switch Statements
|
||||||
|
```dart
|
||||||
|
enum ConnectionState {
|
||||||
|
disconnected,
|
||||||
|
connecting,
|
||||||
|
connected,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use switch without default to catch all cases
|
||||||
|
Widget buildConnectionIndicator(ConnectionState state) {
|
||||||
|
switch (state) {
|
||||||
|
case ConnectionState.disconnected:
|
||||||
|
return const Icon(Icons.wifi_off, color: Colors.grey);
|
||||||
|
case ConnectionState.connecting:
|
||||||
|
return const CircularProgressIndicator();
|
||||||
|
case ConnectionState.connected:
|
||||||
|
return const Icon(Icons.wifi, color: Colors.green);
|
||||||
|
case ConnectionState.error:
|
||||||
|
return const Icon(Icons.error, color: Colors.red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Effective Use of Assert
|
||||||
|
```dart
|
||||||
|
class Rectangle {
|
||||||
|
const Rectangle({
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
|
}) : assert(width > 0, 'Width must be positive'),
|
||||||
|
assert(height > 0, 'Height must be positive');
|
||||||
|
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
|
||||||
|
double get area {
|
||||||
|
assert(width > 0 && height > 0, 'Invalid rectangle dimensions');
|
||||||
|
return width * height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance and Best Practices
|
||||||
|
|
||||||
|
### Const Constructors and Optimization
|
||||||
|
```dart
|
||||||
|
// Use const constructors when possible
|
||||||
|
const EdgeInsets.all(16.0);
|
||||||
|
const SizedBox(height: 8.0);
|
||||||
|
|
||||||
|
// Create const widgets for better performance
|
||||||
|
class LoadingIndicator extends StatelessWidget {
|
||||||
|
const LoadingIndicator({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Efficient Widget Building
|
||||||
|
```dart
|
||||||
|
class ProductList extends StatelessWidget {
|
||||||
|
const ProductList({
|
||||||
|
super.key,
|
||||||
|
required this.products,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Product> products;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Don't do heavy computation in build method
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: products.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final product = products[index];
|
||||||
|
return ProductTile(
|
||||||
|
key: ValueKey(product.id),
|
||||||
|
product: product,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
### Common Mistakes
|
||||||
|
```dart
|
||||||
|
// DON'T use double negatives
|
||||||
|
bool get isNotDisabled => !disabled; // Confusing
|
||||||
|
|
||||||
|
// DO use positive naming
|
||||||
|
bool get isEnabled => !disabled; // Clear
|
||||||
|
|
||||||
|
// DON'T use global state
|
||||||
|
var globalCounter = 0; // Avoid
|
||||||
|
|
||||||
|
// DO use proper state management
|
||||||
|
class CounterProvider extends ChangeNotifier {
|
||||||
|
int _counter = 0;
|
||||||
|
int get counter => _counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DON'T create classes with only static members
|
||||||
|
class MathUtils {
|
||||||
|
static double pi = 3.14159;
|
||||||
|
static double circleArea(double radius) => pi * radius * radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO use top-level functions and constants
|
||||||
|
const double pi = 3.14159;
|
||||||
|
double circleArea(double radius) => pi * radius * radius;
|
||||||
|
|
||||||
|
// DON'T avoid using APIs as intended
|
||||||
|
class TimeSlot {
|
||||||
|
TimeSlot(this.start, this.end);
|
||||||
|
DateTime start;
|
||||||
|
DateTime end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO follow API design principles
|
||||||
|
class TimeSlot {
|
||||||
|
const TimeSlot({
|
||||||
|
required this.start,
|
||||||
|
required this.end,
|
||||||
|
}) : assert(start.isBefore(end), 'Start must be before end');
|
||||||
|
|
||||||
|
final DateTime start;
|
||||||
|
final DateTime end;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tools and Development Workflow
|
||||||
|
|
||||||
|
### Required Tools
|
||||||
|
- **`dart format`**: Automatic code formatting (mandatory)
|
||||||
|
- **`dart analyze`**: Static analysis and linting
|
||||||
|
- **`flutter test`**: Run tests
|
||||||
|
- **IDE setup**: Configure your IDE to run these tools automatically
|
||||||
|
- **Pre-commit hooks**: Ensure code quality before commits
|
||||||
|
|
||||||
|
### Code Quality Checklist
|
||||||
|
- [ ] All code formatted with `dart format`
|
||||||
|
- [ ] No analyzer warnings or errors
|
||||||
|
- [ ] All public APIs documented with `///` comments
|
||||||
|
- [ ] Tests written for new functionality
|
||||||
|
- [ ] Error messages are specific and actionable
|
||||||
|
- [ ] Type annotations present on all public APIs
|
||||||
|
- [ ] Immutable objects used where appropriate
|
||||||
|
- [ ] Assert statements used to verify contracts
|
||||||
|
|
||||||
|
This unified style guide ensures consistency with both Flutter team practices and official Dart conventions, helping create maintainable, readable code that follows established patterns.
|
||||||
93
.prompts/release.md
Normal file
93
.prompts/release.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
You are an AI developer specializing in Dart and Flutter. Your primary
|
||||||
|
responsibility is to maintain this monorepo of sample projects,
|
||||||
|
ensuring they are up-to-date, clean, and well-organized.
|
||||||
|
|
||||||
|
This workflow is triggered when a new Flutter/Dart version is
|
||||||
|
released. Follow these steps precisely:
|
||||||
|
|
||||||
|
1. Prepare your environment:
|
||||||
|
* Switch to the `beta` branch and ensure it's up-to-date:
|
||||||
|
```bash
|
||||||
|
git checkout beta
|
||||||
|
git pull origin beta
|
||||||
|
```
|
||||||
|
* Switch your local Flutter SDK to the `beta` channel and upgrade:
|
||||||
|
```bash
|
||||||
|
flutter channel beta
|
||||||
|
flutter upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Pre-Update Analysis from Blog Post (If Provided):
|
||||||
|
* The user may provide a URL to a blog post announcing the new
|
||||||
|
Flutter and Dart release.
|
||||||
|
* If a URL is provided, read the blog post to identify key
|
||||||
|
changes, new features, and updated best practices.
|
||||||
|
* Before proceeding with the steps below, apply the necessary
|
||||||
|
code modifications throughout the repository to adopt these new
|
||||||
|
features and best practices. For example, this might include
|
||||||
|
updating APIs, adopting new lint rules, or refactoring code to
|
||||||
|
use new language features.
|
||||||
|
|
||||||
|
3. Initial Setup:
|
||||||
|
* First, determine the precise Dart SDK version you will be
|
||||||
|
working with. Execute the command `flutter --version --machine`.
|
||||||
|
* Parse the JSON output to find the value of dartSdkVersion. You
|
||||||
|
will need the version number (e.g., 3.9.0). Let's call this
|
||||||
|
DART_VERSION.
|
||||||
|
* Next, read the pubspec.yaml file at the root of the monorepo.
|
||||||
|
* Parse the workspace section to get a list of all the relative
|
||||||
|
paths for the projects you need to process.
|
||||||
|
|
||||||
|
4. Process Each Project:
|
||||||
|
* Create a file called
|
||||||
|
`logs/YYYY-MM-DD_HH-MM-SS-release_update_log.txt`, but replace
|
||||||
|
YYYY-MM-DD_HH-MM-SS with the current date/time.
|
||||||
|
* Iterate through each project path you discovered in the
|
||||||
|
workspace.
|
||||||
|
* For each project, perform the following actions in its
|
||||||
|
directory. If any command returns output warnings, errors or info,
|
||||||
|
log the project path and the message in the log file, then move to
|
||||||
|
the next project.
|
||||||
|
|
||||||
|
5. Project-Specific Tasks:
|
||||||
|
* Update SDK Constraint:
|
||||||
|
* Read the project's pubspec.yaml file.
|
||||||
|
* Find the environment.sdk key.
|
||||||
|
* Update its value to ^DART_VERSION-0 (e.g., ^3.9.0-0).
|
||||||
|
* Save the modified pubspec.yaml file.
|
||||||
|
* Run Quality Checks:
|
||||||
|
* Run dart analyze --fatal-infos --fatal-warnings.
|
||||||
|
* Run dart format . to ensure the code is correctly formatted.
|
||||||
|
* Run Tests:
|
||||||
|
* Check if a test directory exists in the project.
|
||||||
|
* Exception: Do not run tests for the project named
|
||||||
|
material_3_demo.
|
||||||
|
* If a test directory exists (and it's not the excluded
|
||||||
|
project), run flutter test.
|
||||||
|
|
||||||
|
6. Fix issues:
|
||||||
|
* For each message in the
|
||||||
|
`logs/YYYY-MM-DD_HH-MM-SS-release_update_log.txt` file, attempt
|
||||||
|
to fix the problem. After 30 seconds of being unable to fix it,
|
||||||
|
move onto the next issue.
|
||||||
|
* If you fix the issue successfully, remove the message from the
|
||||||
|
log file.
|
||||||
|
* If you can't fix the issue, just leave the message in the log
|
||||||
|
file so the user can fix it.
|
||||||
|
|
||||||
|
7. Final Report:
|
||||||
|
* After processing all projects, generate a summary report.
|
||||||
|
* The report must include:
|
||||||
|
* The total number of projects processed.
|
||||||
|
* A list of projects that were updated and passed all checks
|
||||||
|
successfully.
|
||||||
|
* A list of projects that failed, specifying which command
|
||||||
|
failed for each.
|
||||||
|
|
||||||
|
8. Create Pull Request:
|
||||||
|
* After generating the report, create a pull request.
|
||||||
|
* Use the `gh` CLI tool for this purpose.
|
||||||
|
* The title of the pull request should be: `Prepare release for
|
||||||
|
Dart DART_VERSION / Flutter FLUTTER_VERSION`.
|
||||||
|
* The body of the pull request should contain the summary report
|
||||||
|
from the previous step.
|
||||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,32 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
The purpose of this changelog is to track the freshness of samples and which
|
|
||||||
samples reflect *current best practices*. It describes **human-made, significant
|
|
||||||
** changes made to the repository or samples in the repository.
|
|
||||||
|
|
||||||
While all samples in this repository build and run, some of them were written
|
|
||||||
long ago, and no longer reflect what we want developers to learn. For example,
|
|
||||||
samples should have been refactored when Dart 3 released to include patterns and
|
|
||||||
records, where appropriate.
|
|
||||||
|
|
||||||
* **DO include:**
|
|
||||||
* The addition of new samples.
|
|
||||||
* The removal of existing samples.
|
|
||||||
* Considerable refactoring of any given sample.
|
|
||||||
|
|
||||||
* **DO NOT include:**
|
|
||||||
* Simple changes that reflect minor version bumps in Flutter. For example,
|
|
||||||
in a recent Flutter update, `Color.red` became `Color.r`.
|
|
||||||
* Dependency updates.
|
|
||||||
* Bug fixes.
|
|
||||||
* Any changes made to simply 'keep the lights on'.
|
|
||||||
|
|
||||||
# Log
|
|
||||||
|
|
||||||
| DATE (YYYY-MM-DD) | Sample(s) | author | Changes |
|
|
||||||
|-------------------|-------------------|--------------|-----------------------------------------------|
|
|
||||||
| NEXT GOES HERE | | | |
|
|
||||||
| | | | |
|
|
||||||
| 2024-12-04 | N/A - repo change | ericwindmill | Added changelog |
|
|
||||||
| 2024-11-27 | fake_sample | ericwindmill | Refactored fake_sample to use Dart 3 features |
|
|
||||||
| 2020-04-17 | fake_sample | ericwindmill | Created fake_sample |
|
|
||||||
19
README.md
19
README.md
@@ -7,7 +7,7 @@ A collection of open source samples that illustrate best practices for
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
We're very appreciative of fixes and necessary improvements to the existing samples. **But in most cases, we're not currently adding new samples to this repository** while we rethink sample code in the post-LLM world.
|
We appreciate fixes and necessary improvements to existing samples. **But in most cases, we're not currently adding new samples to this repository** while we rethink sample code in the new LLM world.
|
||||||
|
|
||||||
Please read the [contributor's guide] if you have contributions.
|
Please read the [contributor's guide] if you have contributions.
|
||||||
|
|
||||||
@@ -105,23 +105,6 @@ See the [getting started guide] to install the `flutter` tool.
|
|||||||
> If you want to run an add-to-app sample, there are additional requirements.
|
> If you want to run an add-to-app sample, there are additional requirements.
|
||||||
> We suggest reading the [add-to-app documentation].
|
> We suggest reading the [add-to-app documentation].
|
||||||
|
|
||||||
### Tip: minimize download size
|
|
||||||
|
|
||||||
As this repository is quite big, you can use
|
|
||||||
[svn] to download a single example.
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```
|
|
||||||
svn co https://github.com/flutter/samples/trunk/provider_shopper
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also use a [partial clone] to skip blob objects
|
|
||||||
that aren't currently checked out, while including the full commit history:
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone --filter=blob:none https://github.com/flutter/samples.git
|
|
||||||
```
|
|
||||||
|
|
||||||
## Interested in contributing?
|
## Interested in contributing?
|
||||||
|
|
||||||
See the [contributor's guide]!
|
See the [contributor's guide]!
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ class Cell extends StatefulWidget {
|
|||||||
|
|
||||||
class _CellState extends State<Cell> with WidgetsBindingObserver {
|
class _CellState extends State<Cell> with WidgetsBindingObserver {
|
||||||
static const double gravity = 9.81;
|
static const double gravity = 9.81;
|
||||||
static final AccelerometerEvent defaultPosition = AccelerometerEvent(0, 0, 0);
|
static final AccelerometerEvent defaultPosition = AccelerometerEvent(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
int cellNumber = 0;
|
int cellNumber = 0;
|
||||||
Random? _random;
|
Random? _random;
|
||||||
@@ -82,7 +87,10 @@ class _CellState extends State<Cell> with WidgetsBindingObserver {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Card(
|
return Card(
|
||||||
// Mimic the platform Material look.
|
// Mimic the platform Material look.
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 36, vertical: 24),
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 36,
|
||||||
|
vertical: 24,
|
||||||
|
),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
@@ -112,22 +120,22 @@ class _CellState extends State<Cell> with WidgetsBindingObserver {
|
|||||||
child: StreamBuilder<AccelerometerEvent>(
|
child: StreamBuilder<AccelerometerEvent>(
|
||||||
// Don't continuously rebuild for nothing when the
|
// Don't continuously rebuild for nothing when the
|
||||||
// cell isn't visible.
|
// cell isn't visible.
|
||||||
stream:
|
stream: appLifecycleState == AppLifecycleState.resumed
|
||||||
appLifecycleState == AppLifecycleState.resumed
|
? accelerometerEventStream()
|
||||||
? accelerometerEventStream()
|
: Stream.value(defaultPosition),
|
||||||
: Stream.value(defaultPosition),
|
|
||||||
initialData: defaultPosition,
|
initialData: defaultPosition,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Transform(
|
return Transform(
|
||||||
// Figure out the phone's orientation relative
|
// Figure out the phone's orientation relative
|
||||||
// to gravity's direction. Ignore the z vector.
|
// to gravity's direction. Ignore the z vector.
|
||||||
transform: Matrix4.rotationX(
|
transform:
|
||||||
snapshot.data!.y / gravity * pi / 2,
|
Matrix4.rotationX(
|
||||||
)..multiply(
|
snapshot.data!.y / gravity * pi / 2,
|
||||||
Matrix4.rotationY(
|
)..multiply(
|
||||||
snapshot.data!.x / gravity * pi / 2,
|
Matrix4.rotationY(
|
||||||
),
|
snapshot.data!.x / gravity * pi / 2,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const FlutterLogo(size: 72),
|
child: const FlutterLogo(size: 72),
|
||||||
);
|
);
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
name: flutter_module_using_plugin
|
name: flutter_module_using_plugin_android_view
|
||||||
description: An example Flutter module that uses a plugin.
|
description: An example Flutter module that uses a plugin.
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.8.1-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
provider: ^6.0.2
|
provider: ^6.1.5
|
||||||
url_launcher: ^6.0.20
|
url_launcher: ^6.3.2
|
||||||
sensors_plus: ^5.0.1
|
sensors_plus: ^6.1.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
analysis_defaults:
|
analysis_defaults:
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
// tree, read text, and verify that the values of widget properties are correct.
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_module_using_plugin/main.dart';
|
import 'package:flutter_module_using_plugin_android_view/main.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -125,28 +125,26 @@ class _BookDetailState extends State<BookDetail> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.check),
|
icon: const Icon(Icons.check),
|
||||||
// Pressing save sends the updated book to the platform.
|
// Pressing save sends the updated book to the platform.
|
||||||
onPressed:
|
onPressed: book != null
|
||||||
book != null
|
? () {
|
||||||
? () {
|
hostApi.finishEditingBook(book!);
|
||||||
hostApi.finishEditingBook(book!);
|
clear();
|
||||||
clear();
|
}
|
||||||
}
|
: null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body:
|
body: book == null
|
||||||
book == null
|
// Draw a spinner until the platform gives us the book to show details
|
||||||
// Draw a spinner until the platform gives us the book to show details
|
// for.
|
||||||
// for.
|
? const Center(child: CircularProgressIndicator())
|
||||||
? const Center(child: CircularProgressIndicator())
|
: BookForm(
|
||||||
: BookForm(
|
book: book!,
|
||||||
book: book!,
|
focusNode: textFocusNode,
|
||||||
focusNode: textFocusNode,
|
authorTextController: authorTextController,
|
||||||
authorTextController: authorTextController,
|
subtitleTextController: subtitleTextController,
|
||||||
subtitleTextController: subtitleTextController,
|
titleTextController: titleTextController,
|
||||||
titleTextController: titleTextController,
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ name: flutter_module_books
|
|||||||
description: A Flutter module using the Pigeon package to demonstrate
|
description: A Flutter module using the Pigeon package to demonstrate
|
||||||
integrating Flutter in a realistic scenario where the existing platform app
|
integrating Flutter in a realistic scenario where the existing platform app
|
||||||
already has business logic and middleware constraints.
|
already has business logic and middleware constraints.
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
@@ -20,11 +20,15 @@ void main() {
|
|||||||
expect(mockHostApi.cancelCalls, 1);
|
expect(mockHostApi.cancelCalls, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Pressing done calls the finish editing API', (tester) async {
|
testWidgets('Pressing done calls the finish editing API', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
MockHostBookApi mockHostApi = MockHostBookApi();
|
MockHostBookApi mockHostApi = MockHostBookApi();
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(home: BookDetail(book: Book(), hostApi: mockHostApi)),
|
MaterialApp(
|
||||||
|
home: BookDetail(book: Book(), hostApi: mockHostApi),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.tap(find.byIcon(Icons.check));
|
await tester.tap(find.byIcon(Icons.check));
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
name: flutter_module
|
name: flutter_module_fullscreen
|
||||||
description: An example Flutter module.
|
description: An example Flutter module.
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_module/main.dart';
|
import 'package:flutter_module_fullscreen/main.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter_driver/driver_extension.dart';
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
import 'package:flutter_module/main.dart' as app;
|
import 'package:flutter_module_fullscreen/main.dart' as app;
|
||||||
|
|
||||||
// This alternate entrypoint is used for espresso testing. See
|
// This alternate entrypoint is used for espresso testing. See
|
||||||
// https://pub.dev/packages/espresso for details.
|
// https://pub.dev/packages/espresso for details.
|
||||||
@@ -82,7 +82,10 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
'$_counter',
|
'$_counter',
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
),
|
),
|
||||||
TextButton(onPressed: _incrementCounter, child: const Text('Add')),
|
TextButton(
|
||||||
|
onPressed: _incrementCounter,
|
||||||
|
child: const Text('Add'),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_channel.invokeMethod<void>("next", _counter);
|
_channel.invokeMethod<void>("next", _counter);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
name: multiple_flutters_module
|
name: multiple_flutters_module
|
||||||
description: A module that is embedded in the multiple_flutters_ios and multiple_flutters_android sample code.
|
description: A module that is embedded in the multiple_flutters_ios and multiple_flutters_android sample code.
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ class Cell extends StatefulWidget {
|
|||||||
|
|
||||||
class _CellState extends State<Cell> with WidgetsBindingObserver {
|
class _CellState extends State<Cell> with WidgetsBindingObserver {
|
||||||
static const double gravity = 9.81;
|
static const double gravity = 9.81;
|
||||||
static final AccelerometerEvent defaultPosition = AccelerometerEvent(0, 0, 0);
|
static final AccelerometerEvent defaultPosition = AccelerometerEvent(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
int cellNumber = 0;
|
int cellNumber = 0;
|
||||||
Random? _random;
|
Random? _random;
|
||||||
@@ -82,7 +87,10 @@ class _CellState extends State<Cell> with WidgetsBindingObserver {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Card(
|
return Card(
|
||||||
// Mimic the platform Material look.
|
// Mimic the platform Material look.
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 36, vertical: 24),
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 36,
|
||||||
|
vertical: 24,
|
||||||
|
),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
@@ -112,10 +120,9 @@ class _CellState extends State<Cell> with WidgetsBindingObserver {
|
|||||||
child: StreamBuilder<AccelerometerEvent>(
|
child: StreamBuilder<AccelerometerEvent>(
|
||||||
// Don't continuously rebuild for nothing when the
|
// Don't continuously rebuild for nothing when the
|
||||||
// cell isn't visible.
|
// cell isn't visible.
|
||||||
stream:
|
stream: appLifecycleState == AppLifecycleState.resumed
|
||||||
appLifecycleState == AppLifecycleState.resumed
|
? accelerometerEventStream()
|
||||||
? accelerometerEventStream()
|
: Stream.value(defaultPosition),
|
||||||
: Stream.value(defaultPosition),
|
|
||||||
initialData: defaultPosition,
|
initialData: defaultPosition,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final data = snapshot.data;
|
final data = snapshot.data;
|
||||||
@@ -125,11 +132,14 @@ class _CellState extends State<Cell> with WidgetsBindingObserver {
|
|||||||
return Transform(
|
return Transform(
|
||||||
// Figure out the phone's orientation relative
|
// Figure out the phone's orientation relative
|
||||||
// to gravity's direction. Ignore the z vector.
|
// to gravity's direction. Ignore the z vector.
|
||||||
transform: Matrix4.rotationX(
|
transform:
|
||||||
data.y / gravity * pi / 2,
|
Matrix4.rotationX(
|
||||||
)..multiply(
|
data.y / gravity * pi / 2,
|
||||||
Matrix4.rotationY(data.x / gravity * pi / 2),
|
)..multiply(
|
||||||
),
|
Matrix4.rotationY(
|
||||||
|
data.x / gravity * pi / 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const FlutterLogo(size: 72),
|
child: const FlutterLogo(size: 72),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
name: flutter_module_using_plugin
|
name: flutter_module_using_plugin
|
||||||
description: An example Flutter module that uses a plugin.
|
description: An example Flutter module that uses a plugin.
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
provider: ^6.0.2
|
provider: ^6.0.2
|
||||||
url_launcher: ^6.0.20
|
url_launcher: ^6.0.20
|
||||||
sensors_plus: ^5.0.1
|
sensors_plus: ^6.1.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
analysis_defaults:
|
analysis_defaults:
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
name: flutter_module
|
name: flutter_module
|
||||||
description: An example Flutter module.
|
description: An example Flutter module.
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
1
analysis_defaults/.gitignore
vendored
1
analysis_defaults/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
# https://dart.dev/guides/libraries/private-files
|
# https://dart.dev/guides/libraries/private-files
|
||||||
# Created by `dart pub`
|
# Created by `dart pub`
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
|
.build/
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
formatter:
|
||||||
|
trailing_commas: preserve
|
||||||
|
page_width: 79
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
language:
|
language:
|
||||||
strict-casts: true
|
strict-casts: true
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
name: analysis_defaults
|
name: analysis_defaults
|
||||||
description: Analysis defaults for flutter/samples
|
description: Analysis defaults for flutter/samples
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
# NOTE: Code is not allowed in this package. Do not add more dependencies.
|
# NOTE: Code is not allowed in this package. Do not add more dependencies.
|
||||||
# The `flutter_lints` dependency is required for `lib/flutter.yaml`.
|
# The `flutter_lints` dependency is required for `lib/flutter.yaml`.
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
name: splash_screen_sample
|
name: splash_screen_sample
|
||||||
description: A sample Flutter app with animated splash screen on Android 12.
|
description: A sample Flutter app with animated splash screen on Android 12.
|
||||||
|
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
analysis_defaults:
|
analysis_defaults:
|
||||||
path: ../analysis_defaults
|
path: ../analysis_defaults
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ const double windowWidth = 480;
|
|||||||
const double windowHeight = 854;
|
const double windowHeight = 854;
|
||||||
|
|
||||||
void setupWindow() {
|
void setupWindow() {
|
||||||
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
if (!kIsWeb &&
|
||||||
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
setWindowTitle('Animation Samples');
|
setWindowTitle('Animation Samples');
|
||||||
setWindowMinSize(const Size(windowWidth, windowHeight));
|
setWindowMinSize(const Size(windowWidth, windowHeight));
|
||||||
@@ -43,7 +44,11 @@ class Demo {
|
|||||||
final String route;
|
final String route;
|
||||||
final WidgetBuilder builder;
|
final WidgetBuilder builder;
|
||||||
|
|
||||||
const Demo({required this.name, required this.route, required this.builder});
|
const Demo({
|
||||||
|
required this.name,
|
||||||
|
required this.route,
|
||||||
|
required this.builder,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final basicDemos = [
|
final basicDemos = [
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ class _AnimatedBuilderDemoState extends State<AnimatedBuilderDemo>
|
|||||||
animation: animation,
|
animation: animation,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: animation.value),
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: animation.value,
|
||||||
|
),
|
||||||
child: child,
|
child: child,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
switch (controller.status) {
|
switch (controller.status) {
|
||||||
|
|||||||
@@ -49,18 +49,21 @@ class _FadeTransitionDemoState extends State<FadeTransitionDemo>
|
|||||||
children: [
|
children: [
|
||||||
FadeTransition(
|
FadeTransition(
|
||||||
opacity: _animation,
|
opacity: _animation,
|
||||||
child: const Icon(Icons.star, color: Colors.amber, size: 300),
|
child: const Icon(
|
||||||
|
Icons.star,
|
||||||
|
color: Colors.amber,
|
||||||
|
size: 300,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: const Text('animate'),
|
child: const Text('animate'),
|
||||||
onPressed:
|
onPressed: () => setState(() {
|
||||||
() => setState(() {
|
_controller
|
||||||
_controller
|
.animateTo(1.0)
|
||||||
.animateTo(1.0)
|
.then<TickerFuture>(
|
||||||
.then<TickerFuture>(
|
(value) => _controller.animateBack(0.0),
|
||||||
(value) => _controller.animateBack(0.0),
|
);
|
||||||
);
|
}),
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -74,7 +74,10 @@ class _TweenSequenceDemoState extends State<TweenSequenceDemo>
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: const Text('Animate', style: TextStyle(color: Colors.white)),
|
child: const Text(
|
||||||
|
'Animate',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ class _AnimatedListDemoState extends State<AnimatedListDemo> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('AnimatedList'),
|
title: const Text('AnimatedList'),
|
||||||
actions: [IconButton(icon: const Icon(Icons.add), onPressed: addUser)],
|
actions: [
|
||||||
|
IconButton(icon: const Icon(Icons.add), onPressed: addUser),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: AnimatedList(
|
child: AnimatedList(
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ class AnimatedPositionedDemo extends StatefulWidget {
|
|||||||
static String routeName = 'misc/animated_positioned';
|
static String routeName = 'misc/animated_positioned';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AnimatedPositionedDemo> createState() => _AnimatedPositionedDemoState();
|
State<AnimatedPositionedDemo> createState() =>
|
||||||
|
_AnimatedPositionedDemoState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
|
class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
|
||||||
@@ -54,12 +55,11 @@ class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
|
|||||||
left: leftPosition,
|
left: leftPosition,
|
||||||
duration: const Duration(seconds: 1),
|
duration: const Duration(seconds: 1),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap:
|
onTap: () => changePosition(
|
||||||
() => changePosition(
|
size.height -
|
||||||
size.height -
|
(appBar.preferredSize.height + topPadding + 50),
|
||||||
(appBar.preferredSize.height + topPadding + 50),
|
size.width - 150,
|
||||||
size.width - 150,
|
),
|
||||||
),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
width: 150,
|
width: 150,
|
||||||
@@ -68,8 +68,9 @@ class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Click Me',
|
'Click Me',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(context).buttonTheme.colorScheme!.onPrimary,
|
context,
|
||||||
|
).buttonTheme.colorScheme!.onPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ class _AnimatedSwitcherDemoState extends State<AnimatedSwitcherDemo> {
|
|||||||
title: const Text('AnimatedSwitcher'),
|
title: const Text('AnimatedSwitcher'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed: () =>
|
||||||
() => setState(() => container = generateContainer(++keyCount)),
|
setState(() => container = generateContainer(++keyCount)),
|
||||||
child: const Text('Change Widget'),
|
child: const Text('Change Widget'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -61,9 +61,8 @@ class _AnimatedSwitcherDemoState extends State<AnimatedSwitcherDemo> {
|
|||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: const Duration(seconds: 1),
|
duration: const Duration(seconds: 1),
|
||||||
child: container,
|
child: container,
|
||||||
transitionBuilder:
|
transitionBuilder: (child, animation) =>
|
||||||
(child, animation) =>
|
ScaleTransition(scale: animation, child: child),
|
||||||
ScaleTransition(scale: animation, child: child),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ class CarouselDemo extends StatelessWidget {
|
|||||||
'assets/eat_sydney_sm.jpg',
|
'assets/eat_sydney_sm.jpg',
|
||||||
];
|
];
|
||||||
|
|
||||||
final List<Widget> images =
|
final List<Widget> images = fileNames
|
||||||
fileNames.map((file) => Image.asset(file, fit: BoxFit.cover)).toList();
|
.map((file) => Image.asset(file, fit: BoxFit.cover))
|
||||||
|
.toList();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(context) {
|
Widget build(context) {
|
||||||
@@ -77,29 +78,32 @@ class _CarouselState extends State<Carousel> {
|
|||||||
},
|
},
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
scrollBehavior: ScrollConfiguration.of(context).copyWith(
|
scrollBehavior: ScrollConfiguration.of(context).copyWith(
|
||||||
dragDevices: {ui.PointerDeviceKind.touch, ui.PointerDeviceKind.mouse},
|
dragDevices: {
|
||||||
|
ui.PointerDeviceKind.touch,
|
||||||
|
ui.PointerDeviceKind.mouse,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
itemBuilder:
|
itemBuilder: (context, index) => AnimatedBuilder(
|
||||||
(context, index) => AnimatedBuilder(
|
animation: _controller,
|
||||||
animation: _controller,
|
builder: (context, child) {
|
||||||
builder: (context, child) {
|
var result = _pageHasChanged
|
||||||
var result =
|
? _controller.page!
|
||||||
_pageHasChanged ? _controller.page! : _currentPage * 1.0;
|
: _currentPage * 1.0;
|
||||||
|
|
||||||
// The horizontal position of the page between a 1 and 0
|
// The horizontal position of the page between a 1 and 0
|
||||||
var value = result - index;
|
var value = result - index;
|
||||||
value = (1 - (value.abs() * .5)).clamp(0.0, 1.0);
|
value = (1 - (value.abs() * .5)).clamp(0.0, 1.0);
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: Curves.easeOut.transform(value) * size.height,
|
height: Curves.easeOut.transform(value) * size.height,
|
||||||
width: Curves.easeOut.transform(value) * size.width,
|
width: Curves.easeOut.transform(value) * size.width,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: widget.itemBuilder(context, index),
|
child: widget.itemBuilder(context, index),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,13 +91,12 @@ class _CurvedAnimationDemoState extends State<CurvedAnimationDemo>
|
|||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
DropdownButton<CurveChoice>(
|
DropdownButton<CurveChoice>(
|
||||||
items:
|
items: curves.map((curve) {
|
||||||
curves.map((curve) {
|
return DropdownMenuItem<CurveChoice>(
|
||||||
return DropdownMenuItem<CurveChoice>(
|
value: curve,
|
||||||
value: curve,
|
child: Text(curve.name),
|
||||||
child: Text(curve.name),
|
);
|
||||||
);
|
}).toList(),
|
||||||
}).toList(),
|
|
||||||
onChanged: (newCurve) {
|
onChanged: (newCurve) {
|
||||||
if (newCurve != null) {
|
if (newCurve != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -114,13 +113,12 @@ class _CurvedAnimationDemoState extends State<CurvedAnimationDemo>
|
|||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
DropdownButton<CurveChoice>(
|
DropdownButton<CurveChoice>(
|
||||||
items:
|
items: curves.map((curve) {
|
||||||
curves.map((curve) {
|
return DropdownMenuItem<CurveChoice>(
|
||||||
return DropdownMenuItem<CurveChoice>(
|
value: curve,
|
||||||
value: curve,
|
child: Text(curve.name),
|
||||||
child: Text(curve.name),
|
);
|
||||||
);
|
}).toList(),
|
||||||
}).toList(),
|
|
||||||
onChanged: (newCurve) {
|
onChanged: (newCurve) {
|
||||||
if (newCurve != null) {
|
if (newCurve != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
@@ -52,26 +52,24 @@ class _ExpandCardState extends State<ExpandCard>
|
|||||||
duration: duration,
|
duration: duration,
|
||||||
firstCurve: Curves.easeInOutCubic,
|
firstCurve: Curves.easeInOutCubic,
|
||||||
secondCurve: Curves.easeInOutCubic,
|
secondCurve: Curves.easeInOutCubic,
|
||||||
crossFadeState:
|
crossFadeState: selected
|
||||||
selected
|
? CrossFadeState.showSecond
|
||||||
? CrossFadeState.showSecond
|
: CrossFadeState.showFirst,
|
||||||
: CrossFadeState.showFirst,
|
|
||||||
// Use Positioned.fill() to pass the constraints to its children.
|
// Use Positioned.fill() to pass the constraints to its children.
|
||||||
// This allows the Images to use BoxFit.cover to cover the correct
|
// This allows the Images to use BoxFit.cover to cover the correct
|
||||||
// size
|
// size
|
||||||
layoutBuilder: (
|
layoutBuilder:
|
||||||
topChild,
|
(topChild, topChildKey, bottomChild, bottomChildKey) {
|
||||||
topChildKey,
|
return Stack(
|
||||||
bottomChild,
|
children: [
|
||||||
bottomChildKey,
|
Positioned.fill(
|
||||||
) {
|
key: bottomChildKey,
|
||||||
return Stack(
|
child: bottomChild,
|
||||||
children: [
|
),
|
||||||
Positioned.fill(key: bottomChildKey, child: bottomChild),
|
Positioned.fill(key: topChildKey, child: topChild),
|
||||||
Positioned.fill(key: topChildKey, child: topChild),
|
],
|
||||||
],
|
);
|
||||||
);
|
},
|
||||||
},
|
|
||||||
firstChild: Image.asset(
|
firstChild: Image.asset(
|
||||||
'assets/eat_cape_town_sm.jpg',
|
'assets/eat_cape_town_sm.jpg',
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
|||||||
@@ -18,20 +18,21 @@ class FlutterAnimateDemo extends StatelessWidget {
|
|||||||
body: Center(
|
body: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Text(
|
child:
|
||||||
"Hello Flutter Animate",
|
Text(
|
||||||
style: Theme.of(context).textTheme.headlineLarge,
|
"Hello Flutter Animate",
|
||||||
)
|
style: Theme.of(context).textTheme.headlineLarge,
|
||||||
.animate(onPlay: (controller) => controller.repeat())
|
)
|
||||||
.then(delay: 250.ms)
|
.animate(onPlay: (controller) => controller.repeat())
|
||||||
.fadeIn(duration: 500.ms)
|
.then(delay: 250.ms)
|
||||||
.then(delay: 250.ms)
|
.fadeIn(duration: 500.ms)
|
||||||
.shimmer(duration: 400.ms)
|
.then(delay: 250.ms)
|
||||||
.then(delay: 250.ms)
|
.shimmer(duration: 400.ms)
|
||||||
.slide()
|
.then(delay: 250.ms)
|
||||||
.then(delay: 250.ms)
|
.slide()
|
||||||
.blur(duration: 500.ms)
|
.then(delay: 250.ms)
|
||||||
.then(delay: 100.ms),
|
.blur(duration: 500.ms)
|
||||||
|
.then(delay: 100.ms),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,10 +29,12 @@ class Grid extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return (index >= 20)
|
return (index >= 20)
|
||||||
? const SmallCard(imageAssetName: 'assets/eat_cape_town_sm.jpg')
|
? const SmallCard(
|
||||||
|
imageAssetName: 'assets/eat_cape_town_sm.jpg',
|
||||||
|
)
|
||||||
: const SmallCard(
|
: const SmallCard(
|
||||||
imageAssetName: 'assets/eat_new_orleans_sm.jpg',
|
imageAssetName: 'assets/eat_new_orleans_sm.jpg',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -50,7 +52,9 @@ Route _createRoute(BuildContext parentContext, String image) {
|
|||||||
).chain(CurveTween(curve: Curves.ease)).animate(animation);
|
).chain(CurveTween(curve: Curves.ease)).animate(animation);
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [PositionedTransition(rect: rectAnimation, child: child)],
|
children: [
|
||||||
|
PositionedTransition(rect: rectAnimation, child: child),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,10 +15,15 @@ class HeroAnimationDemo extends StatelessWidget {
|
|||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
child: Hero(
|
child: Hero(
|
||||||
tag: 'hero-page-child',
|
tag: 'hero-page-child',
|
||||||
child: _createHeroContainer(size: 50.0, color: Colors.grey.shade300),
|
child: _createHeroContainer(
|
||||||
|
size: 50.0,
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap:
|
onTap: () =>
|
||||||
() => Navigator.of(context).push<void>(
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
).push<void>(
|
||||||
MaterialPageRoute(builder: (context) => const HeroPage()),
|
MaterialPageRoute(builder: (context) => const HeroPage()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -52,7 +57,9 @@ StatelessWidget _createHeroContainer({
|
|||||||
height: size,
|
height: size,
|
||||||
width: size,
|
width: size,
|
||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
margin: size < 100.0 ? const EdgeInsets.all(10.0) : const EdgeInsets.all(0),
|
margin: size < 100.0
|
||||||
|
? const EdgeInsets.all(10.0)
|
||||||
|
: const EdgeInsets.all(0),
|
||||||
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
||||||
child: const FlutterLogo(),
|
child: const FlutterLogo(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -78,7 +78,9 @@ class _DraggableCardState extends State<DraggableCard>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = AnimationController.unbounded(vsync: this)
|
_controller = AnimationController.unbounded(vsync: this)
|
||||||
..addListener(() => setState(() => _dragAlignment = _animation.value));
|
..addListener(
|
||||||
|
() => setState(() => _dragAlignment = _animation.value),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -92,17 +94,18 @@ class _DraggableCardState extends State<DraggableCard>
|
|||||||
final size = MediaQuery.of(context).size;
|
final size = MediaQuery.of(context).size;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onPanStart: (details) => _controller.stop(canceled: true),
|
onPanStart: (details) => _controller.stop(canceled: true),
|
||||||
onPanUpdate:
|
onPanUpdate: (details) => setState(
|
||||||
(details) => setState(
|
() => _dragAlignment += Alignment(
|
||||||
() =>
|
details.delta.dx / (size.width / 2),
|
||||||
_dragAlignment += Alignment(
|
details.delta.dy / (size.height / 2),
|
||||||
details.delta.dx / (size.width / 2),
|
),
|
||||||
details.delta.dy / (size.height / 2),
|
),
|
||||||
),
|
onPanEnd: (details) =>
|
||||||
),
|
_runAnimation(details.velocity.pixelsPerSecond, size),
|
||||||
onPanEnd:
|
child: Align(
|
||||||
(details) => _runAnimation(details.velocity.pixelsPerSecond, size),
|
alignment: _dragAlignment,
|
||||||
child: Align(alignment: _dragAlignment, child: Card(child: widget.child)),
|
child: Card(child: widget.child),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ class RepeatingAnimationDemo extends StatefulWidget {
|
|||||||
static String routeName = 'misc/repeating_animation';
|
static String routeName = 'misc/repeating_animation';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RepeatingAnimationDemo> createState() => _RepeatingAnimationDemoState();
|
State<RepeatingAnimationDemo> createState() =>
|
||||||
|
_RepeatingAnimationDemoState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RepeatingAnimationDemoState extends State<RepeatingAnimationDemo>
|
class _RepeatingAnimationDemoState extends State<RepeatingAnimationDemo>
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ name: animations
|
|||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_animate: ^4.1.0
|
flutter_animate: ^4.1.0
|
||||||
go_router: ^16.1.0
|
go_router: ^16.0.0
|
||||||
window_size: # plugin is not yet part of the flutter framework
|
window_size: # plugin is not yet part of the flutter framework
|
||||||
git:
|
git:
|
||||||
url: https://github.com/google/flutter-desktop-embedding.git
|
url: https://github.com/google/flutter-desktop-embedding.git
|
||||||
@@ -22,7 +23,6 @@ dev_dependencies:
|
|||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ void main() {
|
|||||||
|
|
||||||
// Get the initial color of the button.
|
// Get the initial color of the button.
|
||||||
ElevatedButton button = tester.widget(find.byType(ElevatedButton));
|
ElevatedButton button = tester.widget(find.byType(ElevatedButton));
|
||||||
WidgetStateProperty<Color?>? initialColor = button.style!.backgroundColor;
|
WidgetStateProperty<Color?>? initialColor =
|
||||||
|
button.style!.backgroundColor;
|
||||||
|
|
||||||
// Tap the button.
|
// Tap the button.
|
||||||
await tester.tap(find.byType(ElevatedButton));
|
await tester.tap(find.byType(ElevatedButton));
|
||||||
@@ -24,7 +25,8 @@ void main() {
|
|||||||
|
|
||||||
// Get the updated color of the button.
|
// Get the updated color of the button.
|
||||||
button = tester.widget(find.byType(ElevatedButton));
|
button = tester.widget(find.byType(ElevatedButton));
|
||||||
WidgetStateProperty<Color?>? updatedColor = button.style!.backgroundColor;
|
WidgetStateProperty<Color?>? updatedColor =
|
||||||
|
button.style!.backgroundColor;
|
||||||
|
|
||||||
// Check if the color has changed.
|
// Check if the color has changed.
|
||||||
expect(initialColor, isNot(updatedColor));
|
expect(initialColor, isNot(updatedColor));
|
||||||
@@ -35,7 +37,8 @@ void main() {
|
|||||||
|
|
||||||
// Get the initial color of the button.
|
// Get the initial color of the button.
|
||||||
ElevatedButton button = tester.widget(find.byType(ElevatedButton));
|
ElevatedButton button = tester.widget(find.byType(ElevatedButton));
|
||||||
WidgetStateProperty<Color?>? initialColor = button.style!.backgroundColor;
|
WidgetStateProperty<Color?>? initialColor =
|
||||||
|
button.style!.backgroundColor;
|
||||||
|
|
||||||
// Tap the button to trigger the animation but don't wait for it to finish.
|
// Tap the button to trigger the animation but don't wait for it to finish.
|
||||||
await tester.tap(find.byType(ElevatedButton));
|
await tester.tap(find.byType(ElevatedButton));
|
||||||
@@ -44,7 +47,8 @@ void main() {
|
|||||||
|
|
||||||
// Check that the color has changed but not to the final color.
|
// Check that the color has changed but not to the final color.
|
||||||
button = tester.widget(find.byType(ElevatedButton));
|
button = tester.widget(find.byType(ElevatedButton));
|
||||||
WidgetStateProperty<Color?>? changedColor = button.style!.backgroundColor;
|
WidgetStateProperty<Color?>? changedColor =
|
||||||
|
button.style!.backgroundColor;
|
||||||
expect(initialColor, isNot(changedColor));
|
expect(initialColor, isNot(changedColor));
|
||||||
|
|
||||||
// Wait for the animation to finish.
|
// Wait for the animation to finish.
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Check if removed properly.
|
// Check if removed properly.
|
||||||
expect(tester.widgetList(find.byType(Card)).length, lessThan(totalCards));
|
expect(
|
||||||
|
tester.widgetList(find.byType(Card)).length,
|
||||||
|
lessThan(totalCards),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('All cards swiped out', (tester) async {
|
testWidgets('All cards swiped out', (tester) async {
|
||||||
@@ -36,7 +39,10 @@ void main() {
|
|||||||
// Swipe out all cards.
|
// Swipe out all cards.
|
||||||
for (var i = 0; i < totalCards; i++) {
|
for (var i = 0; i < totalCards; i++) {
|
||||||
// Swipe out one by one.
|
// Swipe out one by one.
|
||||||
await tester.drag(find.byType(Card).last, const Offset(100.0, 0.0));
|
await tester.drag(
|
||||||
|
find.byType(Card).last,
|
||||||
|
const Offset(100.0, 0.0),
|
||||||
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ void main() {
|
|||||||
expect(imageList.length, 2);
|
expect(imageList.length, 2);
|
||||||
|
|
||||||
// Swipe the Carousel.
|
// Swipe the Carousel.
|
||||||
await tester.fling(find.byType(CarouselDemo), const Offset(-400, 0), 800);
|
await tester.fling(
|
||||||
|
find.byType(CarouselDemo),
|
||||||
|
const Offset(-400, 0),
|
||||||
|
800,
|
||||||
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Get the images available on the screen after swipe.
|
// Get the images available on the screen after swipe.
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ void main() {
|
|||||||
|
|
||||||
// The size of ExpandCard must change once tapped.
|
// The size of ExpandCard must change once tapped.
|
||||||
// The initialSize should be less than current ExpandCard size.
|
// The initialSize should be less than current ExpandCard size.
|
||||||
expect(initialSize, lessThan(tester.getSize(find.byType(ExpandCard))));
|
expect(
|
||||||
|
initialSize,
|
||||||
|
lessThan(tester.getSize(find.byType(ExpandCard))),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('ExpandCard changes image on tap', (tester) async {
|
testWidgets('ExpandCard changes image on tap', (tester) async {
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ void main() {
|
|||||||
expect(finalSize, greaterThan(initialSize));
|
expect(finalSize, greaterThan(initialSize));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Final inkwell on tap goes back to the grid', (tester) async {
|
testWidgets('Final inkwell on tap goes back to the grid', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
await tester.pumpWidget(createFocusImageScreen());
|
await tester.pumpWidget(createFocusImageScreen());
|
||||||
|
|
||||||
// Tap on the ink well at index 0.
|
// Tap on the ink well at index 0.
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ void main() {
|
|||||||
// Final color should not be same as initial color.
|
// Final color should not be same as initial color.
|
||||||
expect(
|
expect(
|
||||||
(finalContainer.decoration as BoxDecoration).color,
|
(finalContainer.decoration as BoxDecoration).color,
|
||||||
isNot(equals((initialContainer.decoration as BoxDecoration).color)),
|
isNot(
|
||||||
|
equals((initialContainer.decoration as BoxDecoration).color),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,7 +68,9 @@ void main() {
|
|||||||
await tester.pumpWidget(createHeroAnimationDemoScreen());
|
await tester.pumpWidget(createHeroAnimationDemoScreen());
|
||||||
|
|
||||||
// Get the initial Screen.
|
// Get the initial Screen.
|
||||||
final initialScreen = tester.firstWidget(find.byType(HeroAnimationDemo));
|
final initialScreen = tester.firstWidget(
|
||||||
|
find.byType(HeroAnimationDemo),
|
||||||
|
);
|
||||||
|
|
||||||
// Tap on the GestureDetector.
|
// Tap on the GestureDetector.
|
||||||
await tester.tap(find.byType(GestureDetector));
|
await tester.tap(find.byType(GestureDetector));
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
include: package:flutter_lints/flutter.yaml
|
|
||||||
@@ -11,10 +11,9 @@ int main(List<String> arguments) {
|
|||||||
// the `--input` option and one for the `--output` option.
|
// the `--input` option and one for the `--output` option.
|
||||||
// `--input` is the original asset file that this program should transform.
|
// `--input` is the original asset file that this program should transform.
|
||||||
// `--output` is where flutter expects the transformation output to be written to.
|
// `--output` is where flutter expects the transformation output to be written to.
|
||||||
final parser =
|
final parser = ArgParser()
|
||||||
ArgParser()
|
..addOption(inputOptionName, mandatory: true, abbr: 'i')
|
||||||
..addOption(inputOptionName, mandatory: true, abbr: 'i')
|
..addOption(outputOptionName, mandatory: true, abbr: 'o');
|
||||||
..addOption(outputOptionName, mandatory: true, abbr: 'o');
|
|
||||||
|
|
||||||
ArgResults argResults = parser.parse(arguments);
|
ArgResults argResults = parser.parse(arguments);
|
||||||
final String inputFilePath = argResults[inputOptionName];
|
final String inputFilePath = argResults[inputOptionName];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ description: A sample command-line application.
|
|||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.4.2
|
args: ^2.4.2
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ name: asset_transformation
|
|||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -12,9 +13,11 @@ dependencies:
|
|||||||
vector_graphics: ^1.1.11+1
|
vector_graphics: ^1.1.11+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
analysis_defaults:
|
||||||
|
path: ../analysis_defaults
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^6.0.0
|
||||||
vector_graphics_compiler: ^1.1.11+1
|
vector_graphics_compiler: ^1.1.11+1
|
||||||
grayscale_transformer:
|
grayscale_transformer:
|
||||||
path: ./grayscale_transformer
|
path: ./grayscale_transformer
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
.then(
|
.then(
|
||||||
(sharedPreferences) => sharedPreferences.setBool('isDebug', true),
|
(sharedPreferences) => sharedPreferences.setBool('isDebug', true),
|
||||||
);
|
);
|
||||||
final Future<Directory> tempDirFuture =
|
final Future<Directory> tempDirFuture = path_provider
|
||||||
path_provider.getTemporaryDirectory();
|
.getTemporaryDirectory();
|
||||||
|
|
||||||
// Wait until the [SharedPreferences] value is set and the temporary
|
// Wait until the [SharedPreferences] value is set and the temporary
|
||||||
// directory is received before opening the database. If
|
// directory is received before opening the database. If
|
||||||
@@ -130,8 +130,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: SearchBar(
|
child: SearchBar(
|
||||||
hintText: 'Search',
|
hintText: 'Search',
|
||||||
onChanged:
|
onChanged: _database == null
|
||||||
_database == null ? null : (query) => _refresh(query: query),
|
? null
|
||||||
|
: (query) => _refresh(query: query),
|
||||||
trailing: const [Icon(Icons.search), SizedBox(width: 8)],
|
trailing: const [Icon(Icons.search), SizedBox(width: 8)],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -188,7 +188,9 @@ class _SimpleDatabaseServer {
|
|||||||
// [BinaryMessenger] that the Platform Channels will communicate with on
|
// [BinaryMessenger] that the Platform Channels will communicate with on
|
||||||
// the background isolate.
|
// the background isolate.
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
|
BackgroundIsolateBinaryMessenger.ensureInitialized(
|
||||||
|
rootIsolateToken,
|
||||||
|
);
|
||||||
_sendPort.send(const _Command(_Codes.ack, arg0: null));
|
_sendPort.send(const _Command(_Codes.ack, arg0: null));
|
||||||
case _Codes.add:
|
case _Codes.add:
|
||||||
_doAddEntry(command.arg0 as String);
|
_doAddEntry(command.arg0 as String);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
name: background_isolate_channels
|
name: background_isolate_channels
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
@@ -23,6 +24,5 @@ dev_dependencies:
|
|||||||
path: ../analysis_defaults
|
path: ../analysis_defaults
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ name: client
|
|||||||
description: A Flutter app which communicates with a Dart backend using shared business logic.
|
description: A Flutter app which communicates with a Dart backend using shared business logic.
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
@@ -15,7 +16,9 @@ dependencies:
|
|||||||
path: ../shared
|
path: ../shared
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^5.0.0
|
analysis_defaults:
|
||||||
|
path: ../../analysis_defaults
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import 'package:shelf_router/shelf_router.dart';
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
// Configure routes.
|
// Configure routes.
|
||||||
final _router =
|
final _router = Router()
|
||||||
Router()
|
..post('/', _incrementHandler)
|
||||||
..post('/', _incrementHandler)
|
..get('/', _getValueHandler);
|
||||||
..get('/', _getValueHandler);
|
|
||||||
|
|
||||||
Future<Response> _incrementHandler(Request request) async {
|
Future<Response> _incrementHandler(Request request) async {
|
||||||
final incr = Increment.fromJson(json.decode(await request.readAsString()));
|
final incr = Increment.fromJson(json.decode(await request.readAsString()));
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ name: server
|
|||||||
description: A server app using the shelf package and Docker.
|
description: A server app using the shelf package and Docker.
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.0.0
|
args: ^2.0.0
|
||||||
@@ -14,6 +15,8 @@ dependencies:
|
|||||||
path: ../shared
|
path: ../shared
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
analysis_defaults:
|
||||||
|
path: ../../analysis_defaults
|
||||||
http: ^1.0.0
|
http: ^1.0.0
|
||||||
lints: ^6.0.0
|
lints: ^6.0.0
|
||||||
test: ^1.15.0
|
test: ^1.15.0
|
||||||
|
|||||||
@@ -52,11 +52,10 @@ class _$IncrementCopyWithImpl<$Res, $Val extends Increment>
|
|||||||
$Res call({Object? by = null}) {
|
$Res call({Object? by = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
by:
|
by: null == by
|
||||||
null == by
|
? _value.by
|
||||||
? _value.by
|
: by // ignore: cast_nullable_to_non_nullable
|
||||||
: by // ignore: cast_nullable_to_non_nullable
|
as int,
|
||||||
as int,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -89,11 +88,10 @@ class __$$IncrementImplCopyWithImpl<$Res>
|
|||||||
$Res call({Object? by = null}) {
|
$Res call({Object? by = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$IncrementImpl(
|
_$IncrementImpl(
|
||||||
by:
|
by: null == by
|
||||||
null == by
|
? _value.by
|
||||||
? _value.by
|
: by // ignore: cast_nullable_to_non_nullable
|
||||||
: by // ignore: cast_nullable_to_non_nullable
|
as int,
|
||||||
as int,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -189,11 +187,10 @@ class _$CountCopyWithImpl<$Res, $Val extends Count>
|
|||||||
$Res call({Object? value = null}) {
|
$Res call({Object? value = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
value:
|
value: null == value
|
||||||
null == value
|
? _value.value
|
||||||
? _value.value
|
: value // ignore: cast_nullable_to_non_nullable
|
||||||
: value // ignore: cast_nullable_to_non_nullable
|
as int,
|
||||||
as int,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -228,7 +225,7 @@ class __$$CountImplCopyWithImpl<$Res>
|
|||||||
null == value
|
null == value
|
||||||
? _value.value
|
? _value.value
|
||||||
: value // ignore: cast_nullable_to_non_nullable
|
: value // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
name: shared
|
name: shared
|
||||||
description: Common data models required by our client and server
|
description: Common data models required by our client and server
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.0-0
|
sdk: ^3.9.0-0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
freezed_annotation: ">=2.1.0 <4.0.0"
|
freezed_annotation: ">=2.1.0 <4.0.0"
|
||||||
json_annotation: ^4.7.0
|
json_annotation: ^4.7.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
analysis_defaults:
|
||||||
|
path: ../../analysis_defaults
|
||||||
build_runner: ^2.2.1
|
build_runner: ^2.2.1
|
||||||
freezed: ">=2.1.1 <4.0.0"
|
freezed: ">=2.1.1 <4.0.0"
|
||||||
json_serializable: ^6.4.0
|
json_serializable: ^6.4.0
|
||||||
|
|||||||
@@ -36,12 +36,11 @@ import '../domain/use_cases/booking/booking_share_use_case.dart';
|
|||||||
List<SingleChildWidget> _sharedProviders = [
|
List<SingleChildWidget> _sharedProviders = [
|
||||||
Provider(
|
Provider(
|
||||||
lazy: true,
|
lazy: true,
|
||||||
create:
|
create: (context) => BookingCreateUseCase(
|
||||||
(context) => BookingCreateUseCase(
|
destinationRepository: context.read(),
|
||||||
destinationRepository: context.read(),
|
activityRepository: context.read(),
|
||||||
activityRepository: context.read(),
|
bookingRepository: context.read(),
|
||||||
bookingRepository: context.read(),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
lazy: true,
|
lazy: true,
|
||||||
@@ -57,46 +56,40 @@ List<SingleChildWidget> get providersRemote {
|
|||||||
Provider(create: (context) => ApiClient()),
|
Provider(create: (context) => ApiClient()),
|
||||||
Provider(create: (context) => SharedPreferencesService()),
|
Provider(create: (context) => SharedPreferencesService()),
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
AuthRepositoryRemote(
|
||||||
AuthRepositoryRemote(
|
authApiClient: context.read(),
|
||||||
authApiClient: context.read(),
|
apiClient: context.read(),
|
||||||
apiClient: context.read(),
|
sharedPreferencesService: context.read(),
|
||||||
sharedPreferencesService: context.read(),
|
)
|
||||||
)
|
as AuthRepository,
|
||||||
as AuthRepository,
|
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
DestinationRepositoryRemote(apiClient: context.read())
|
||||||
DestinationRepositoryRemote(apiClient: context.read())
|
as DestinationRepository,
|
||||||
as DestinationRepository,
|
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
ContinentRepositoryRemote(apiClient: context.read())
|
||||||
ContinentRepositoryRemote(apiClient: context.read())
|
as ContinentRepository,
|
||||||
as ContinentRepository,
|
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
ActivityRepositoryRemote(apiClient: context.read())
|
||||||
ActivityRepositoryRemote(apiClient: context.read())
|
as ActivityRepository,
|
||||||
as ActivityRepository,
|
|
||||||
),
|
),
|
||||||
Provider.value(
|
Provider.value(
|
||||||
value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository,
|
value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository,
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
BookingRepositoryRemote(apiClient: context.read())
|
||||||
BookingRepositoryRemote(apiClient: context.read())
|
as BookingRepository,
|
||||||
as BookingRepository,
|
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
UserRepositoryRemote(apiClient: context.read()) as UserRepository,
|
||||||
UserRepositoryRemote(apiClient: context.read()) as UserRepository,
|
|
||||||
),
|
),
|
||||||
..._sharedProviders,
|
..._sharedProviders,
|
||||||
];
|
];
|
||||||
@@ -110,37 +103,32 @@ List<SingleChildWidget> get providersLocal {
|
|||||||
ChangeNotifierProvider.value(value: AuthRepositoryDev() as AuthRepository),
|
ChangeNotifierProvider.value(value: AuthRepositoryDev() as AuthRepository),
|
||||||
Provider.value(value: LocalDataService()),
|
Provider.value(value: LocalDataService()),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
DestinationRepositoryLocal(localDataService: context.read())
|
||||||
DestinationRepositoryLocal(localDataService: context.read())
|
as DestinationRepository,
|
||||||
as DestinationRepository,
|
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
ContinentRepositoryLocal(localDataService: context.read())
|
||||||
ContinentRepositoryLocal(localDataService: context.read())
|
as ContinentRepository,
|
||||||
as ContinentRepository,
|
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
ActivityRepositoryLocal(localDataService: context.read())
|
||||||
ActivityRepositoryLocal(localDataService: context.read())
|
as ActivityRepository,
|
||||||
as ActivityRepository,
|
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
BookingRepositoryLocal(localDataService: context.read())
|
||||||
BookingRepositoryLocal(localDataService: context.read())
|
as BookingRepository,
|
||||||
as BookingRepository,
|
|
||||||
),
|
),
|
||||||
Provider.value(
|
Provider.value(
|
||||||
value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository,
|
value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository,
|
||||||
),
|
),
|
||||||
Provider(
|
Provider(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
UserRepositoryLocal(localDataService: context.read())
|
||||||
UserRepositoryLocal(localDataService: context.read())
|
as UserRepository,
|
||||||
as UserRepository,
|
|
||||||
),
|
),
|
||||||
..._sharedProviders,
|
..._sharedProviders,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -18,10 +18,9 @@ class ActivityRepositoryLocal implements ActivityRepository {
|
|||||||
@override
|
@override
|
||||||
Future<Result<List<Activity>>> getByDestination(String ref) async {
|
Future<Result<List<Activity>>> getByDestination(String ref) async {
|
||||||
try {
|
try {
|
||||||
final activities =
|
final activities = (await _localDataService.getActivities())
|
||||||
(await _localDataService.getActivities())
|
.where((activity) => activity.destinationRef == ref)
|
||||||
.where((activity) => activity.destinationRef == ref)
|
.toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
return Result.ok(activities);
|
return Result.ok(activities);
|
||||||
} on Exception catch (error) {
|
} on Exception catch (error) {
|
||||||
|
|||||||
@@ -68,11 +68,10 @@ class BookingRepositoryLocal implements BookingRepository {
|
|||||||
// create a default booking the first time
|
// create a default booking the first time
|
||||||
if (_bookings.isEmpty) {
|
if (_bookings.isEmpty) {
|
||||||
final destination = (await _localDataService.getDestinations()).first;
|
final destination = (await _localDataService.getDestinations()).first;
|
||||||
final activities =
|
final activities = (await _localDataService.getActivities())
|
||||||
(await _localDataService.getActivities())
|
.where((activity) => activity.destinationRef == destination.ref)
|
||||||
.where((activity) => activity.destinationRef == destination.ref)
|
.take(4)
|
||||||
.take(4)
|
.toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
_bookings.add(
|
_bookings.add(
|
||||||
Booking(
|
Booking(
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ class BookingRepositoryRemote implements BookingRepository {
|
|||||||
endDate: booking.endDate,
|
endDate: booking.endDate,
|
||||||
name: '${booking.destination.name}, ${booking.destination.continent}',
|
name: '${booking.destination.name}, ${booking.destination.continent}',
|
||||||
destinationRef: booking.destination.ref,
|
destinationRef: booking.destination.ref,
|
||||||
activitiesRef:
|
activitiesRef: booking.activity
|
||||||
booking.activity.map((activity) => activity.ref).toList(),
|
.map((activity) => activity.ref)
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
return _apiClient.postBooking(bookingApiModel);
|
return _apiClient.postBooking(bookingApiModel);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
@@ -72,10 +73,9 @@ class BookingRepositoryRemote implements BookingRepository {
|
|||||||
return Result.error(resultActivities.error);
|
return Result.error(resultActivities.error);
|
||||||
case Ok<List<Activity>>():
|
case Ok<List<Activity>>():
|
||||||
}
|
}
|
||||||
final activities =
|
final activities = resultActivities.value
|
||||||
resultActivities.value
|
.where((activity) => booking.activitiesRef.contains(activity.ref))
|
||||||
.where((activity) => booking.activitiesRef.contains(activity.ref))
|
.toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
return Result.ok(
|
return Result.ok(
|
||||||
Booking(
|
Booking(
|
||||||
|
|||||||
@@ -95,8 +95,9 @@ class ApiClient {
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final stringData = await response.transform(utf8.decoder).join();
|
final stringData = await response.transform(utf8.decoder).join();
|
||||||
final json = jsonDecode(stringData) as List<dynamic>;
|
final json = jsonDecode(stringData) as List<dynamic>;
|
||||||
final activities =
|
final activities = json
|
||||||
json.map((element) => Activity.fromJson(element)).toList();
|
.map((element) => Activity.fromJson(element))
|
||||||
|
.toList();
|
||||||
return Result.ok(activities);
|
return Result.ok(activities);
|
||||||
} else {
|
} else {
|
||||||
return const Result.error(HttpException("Invalid response"));
|
return const Result.error(HttpException("Invalid response"));
|
||||||
@@ -117,8 +118,9 @@ class ApiClient {
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final stringData = await response.transform(utf8.decoder).join();
|
final stringData = await response.transform(utf8.decoder).join();
|
||||||
final json = jsonDecode(stringData) as List<dynamic>;
|
final json = jsonDecode(stringData) as List<dynamic>;
|
||||||
final bookings =
|
final bookings = json
|
||||||
json.map((element) => BookingApiModel.fromJson(element)).toList();
|
.map((element) => BookingApiModel.fromJson(element))
|
||||||
|
.toList();
|
||||||
return Result.ok(bookings);
|
return Result.ok(bookings);
|
||||||
} else {
|
} else {
|
||||||
return const Result.error(HttpException("Invalid response"));
|
return const Result.error(HttpException("Invalid response"));
|
||||||
|
|||||||
@@ -91,36 +91,30 @@ class _$BookingApiModelCopyWithImpl<$Res, $Val extends BookingApiModel>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
id:
|
id: freezed == id
|
||||||
freezed == id
|
? _value.id
|
||||||
? _value.id
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
as int?,
|
||||||
as int?,
|
startDate: null == startDate
|
||||||
startDate:
|
? _value.startDate
|
||||||
null == startDate
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.startDate
|
as DateTime,
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
endDate: null == endDate
|
||||||
as DateTime,
|
? _value.endDate
|
||||||
endDate:
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
null == endDate
|
as DateTime,
|
||||||
? _value.endDate
|
name: null == name
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
? _value.name
|
||||||
as DateTime,
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
name:
|
as String,
|
||||||
null == name
|
destinationRef: null == destinationRef
|
||||||
? _value.name
|
? _value.destinationRef
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: destinationRef // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
destinationRef:
|
activitiesRef: null == activitiesRef
|
||||||
null == destinationRef
|
? _value.activitiesRef
|
||||||
? _value.destinationRef
|
: activitiesRef // ignore: cast_nullable_to_non_nullable
|
||||||
: destinationRef // ignore: cast_nullable_to_non_nullable
|
as List<String>,
|
||||||
as String,
|
|
||||||
activitiesRef:
|
|
||||||
null == activitiesRef
|
|
||||||
? _value.activitiesRef
|
|
||||||
: activitiesRef // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -169,36 +163,30 @@ class __$$BookingApiModelImplCopyWithImpl<$Res>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$BookingApiModelImpl(
|
_$BookingApiModelImpl(
|
||||||
id:
|
id: freezed == id
|
||||||
freezed == id
|
? _value.id
|
||||||
? _value.id
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
as int?,
|
||||||
as int?,
|
startDate: null == startDate
|
||||||
startDate:
|
? _value.startDate
|
||||||
null == startDate
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.startDate
|
as DateTime,
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
endDate: null == endDate
|
||||||
as DateTime,
|
? _value.endDate
|
||||||
endDate:
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
null == endDate
|
as DateTime,
|
||||||
? _value.endDate
|
name: null == name
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
? _value.name
|
||||||
as DateTime,
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
name:
|
as String,
|
||||||
null == name
|
destinationRef: null == destinationRef
|
||||||
? _value.name
|
? _value.destinationRef
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: destinationRef // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
destinationRef:
|
activitiesRef: null == activitiesRef
|
||||||
null == destinationRef
|
? _value._activitiesRef
|
||||||
? _value.destinationRef
|
: activitiesRef // ignore: cast_nullable_to_non_nullable
|
||||||
: destinationRef // ignore: cast_nullable_to_non_nullable
|
as List<String>,
|
||||||
as String,
|
|
||||||
activitiesRef:
|
|
||||||
null == activitiesRef
|
|
||||||
? _value._activitiesRef
|
|
||||||
: activitiesRef // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ _$BookingApiModelImpl _$$BookingApiModelImplFromJson(
|
|||||||
endDate: DateTime.parse(json['endDate'] as String),
|
endDate: DateTime.parse(json['endDate'] as String),
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
destinationRef: json['destinationRef'] as String,
|
destinationRef: json['destinationRef'] as String,
|
||||||
activitiesRef:
|
activitiesRef: (json['activitiesRef'] as List<dynamic>)
|
||||||
(json['activitiesRef'] as List<dynamic>).map((e) => e as String).toList(),
|
.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$BookingApiModelImplToJson(
|
Map<String, dynamic> _$$BookingApiModelImplToJson(
|
||||||
|
|||||||
@@ -64,16 +64,14 @@ class _$LoginRequestCopyWithImpl<$Res, $Val extends LoginRequest>
|
|||||||
$Res call({Object? email = null, Object? password = null}) {
|
$Res call({Object? email = null, Object? password = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
email:
|
email: null == email
|
||||||
null == email
|
? _value.email
|
||||||
? _value.email
|
: email // ignore: cast_nullable_to_non_nullable
|
||||||
: email // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
password: null == password
|
||||||
password:
|
? _value.password
|
||||||
null == password
|
: password // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.password
|
as String,
|
||||||
: password // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -108,16 +106,14 @@ class __$$LoginRequestImplCopyWithImpl<$Res>
|
|||||||
$Res call({Object? email = null, Object? password = null}) {
|
$Res call({Object? email = null, Object? password = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$LoginRequestImpl(
|
_$LoginRequestImpl(
|
||||||
email:
|
email: null == email
|
||||||
null == email
|
? _value.email
|
||||||
? _value.email
|
: email // ignore: cast_nullable_to_non_nullable
|
||||||
: email // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
password: null == password
|
||||||
password:
|
? _value.password
|
||||||
null == password
|
: password // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.password
|
as String,
|
||||||
: password // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,16 +64,14 @@ class _$LoginResponseCopyWithImpl<$Res, $Val extends LoginResponse>
|
|||||||
$Res call({Object? token = null, Object? userId = null}) {
|
$Res call({Object? token = null, Object? userId = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
token:
|
token: null == token
|
||||||
null == token
|
? _value.token
|
||||||
? _value.token
|
: token // ignore: cast_nullable_to_non_nullable
|
||||||
: token // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
userId: null == userId
|
||||||
userId:
|
? _value.userId
|
||||||
null == userId
|
: userId // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.userId
|
as String,
|
||||||
: userId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -108,16 +106,14 @@ class __$$LoginResponseImplCopyWithImpl<$Res>
|
|||||||
$Res call({Object? token = null, Object? userId = null}) {
|
$Res call({Object? token = null, Object? userId = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$LoginResponseImpl(
|
_$LoginResponseImpl(
|
||||||
token:
|
token: null == token
|
||||||
null == token
|
? _value.token
|
||||||
? _value.token
|
: token // ignore: cast_nullable_to_non_nullable
|
||||||
: token // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
userId: null == userId
|
||||||
userId:
|
? _value.userId
|
||||||
null == userId
|
: userId // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.userId
|
as String,
|
||||||
: userId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,26 +75,22 @@ class _$UserApiModelCopyWithImpl<$Res, $Val extends UserApiModel>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
id:
|
id: null == id
|
||||||
null == id
|
? _value.id
|
||||||
? _value.id
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
name: null == name
|
||||||
name:
|
? _value.name
|
||||||
null == name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.name
|
as String,
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
email: null == email
|
||||||
as String,
|
? _value.email
|
||||||
email:
|
: email // ignore: cast_nullable_to_non_nullable
|
||||||
null == email
|
as String,
|
||||||
? _value.email
|
picture: null == picture
|
||||||
: email // ignore: cast_nullable_to_non_nullable
|
? _value.picture
|
||||||
as String,
|
: picture // ignore: cast_nullable_to_non_nullable
|
||||||
picture:
|
as String,
|
||||||
null == picture
|
|
||||||
? _value.picture
|
|
||||||
: picture // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -134,26 +130,22 @@ class __$$UserApiModelImplCopyWithImpl<$Res>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$UserApiModelImpl(
|
_$UserApiModelImpl(
|
||||||
id:
|
id: null == id
|
||||||
null == id
|
? _value.id
|
||||||
? _value.id
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
name: null == name
|
||||||
name:
|
? _value.name
|
||||||
null == name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.name
|
as String,
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
email: null == email
|
||||||
as String,
|
? _value.email
|
||||||
email:
|
: email // ignore: cast_nullable_to_non_nullable
|
||||||
null == email
|
as String,
|
||||||
? _value.email
|
picture: null == picture
|
||||||
: email // ignore: cast_nullable_to_non_nullable
|
? _value.picture
|
||||||
as String,
|
: picture // ignore: cast_nullable_to_non_nullable
|
||||||
picture:
|
as String,
|
||||||
null == picture
|
|
||||||
? _value.picture
|
|
||||||
: picture // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,56 +109,46 @@ class _$ActivityCopyWithImpl<$Res, $Val extends Activity>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
name:
|
name: null == name
|
||||||
null == name
|
? _value.name
|
||||||
? _value.name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
description: null == description
|
||||||
description:
|
? _value.description
|
||||||
null == description
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.description
|
as String,
|
||||||
: description // ignore: cast_nullable_to_non_nullable
|
locationName: null == locationName
|
||||||
as String,
|
? _value.locationName
|
||||||
locationName:
|
: locationName // ignore: cast_nullable_to_non_nullable
|
||||||
null == locationName
|
as String,
|
||||||
? _value.locationName
|
duration: null == duration
|
||||||
: locationName // ignore: cast_nullable_to_non_nullable
|
? _value.duration
|
||||||
as String,
|
: duration // ignore: cast_nullable_to_non_nullable
|
||||||
duration:
|
as int,
|
||||||
null == duration
|
timeOfDay: null == timeOfDay
|
||||||
? _value.duration
|
? _value.timeOfDay
|
||||||
: duration // ignore: cast_nullable_to_non_nullable
|
: timeOfDay // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as TimeOfDay,
|
||||||
timeOfDay:
|
familyFriendly: null == familyFriendly
|
||||||
null == timeOfDay
|
? _value.familyFriendly
|
||||||
? _value.timeOfDay
|
: familyFriendly // ignore: cast_nullable_to_non_nullable
|
||||||
: timeOfDay // ignore: cast_nullable_to_non_nullable
|
as bool,
|
||||||
as TimeOfDay,
|
price: null == price
|
||||||
familyFriendly:
|
? _value.price
|
||||||
null == familyFriendly
|
: price // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.familyFriendly
|
as int,
|
||||||
: familyFriendly // ignore: cast_nullable_to_non_nullable
|
destinationRef: null == destinationRef
|
||||||
as bool,
|
? _value.destinationRef
|
||||||
price:
|
: destinationRef // ignore: cast_nullable_to_non_nullable
|
||||||
null == price
|
as String,
|
||||||
? _value.price
|
ref: null == ref
|
||||||
: price // ignore: cast_nullable_to_non_nullable
|
? _value.ref
|
||||||
as int,
|
: ref // ignore: cast_nullable_to_non_nullable
|
||||||
destinationRef:
|
as String,
|
||||||
null == destinationRef
|
imageUrl: null == imageUrl
|
||||||
? _value.destinationRef
|
? _value.imageUrl
|
||||||
: destinationRef // ignore: cast_nullable_to_non_nullable
|
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
ref:
|
|
||||||
null == ref
|
|
||||||
? _value.ref
|
|
||||||
: ref // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
imageUrl:
|
|
||||||
null == imageUrl
|
|
||||||
? _value.imageUrl
|
|
||||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -215,56 +205,46 @@ class __$$ActivityImplCopyWithImpl<$Res>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$ActivityImpl(
|
_$ActivityImpl(
|
||||||
name:
|
name: null == name
|
||||||
null == name
|
? _value.name
|
||||||
? _value.name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
description: null == description
|
||||||
description:
|
? _value.description
|
||||||
null == description
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.description
|
as String,
|
||||||
: description // ignore: cast_nullable_to_non_nullable
|
locationName: null == locationName
|
||||||
as String,
|
? _value.locationName
|
||||||
locationName:
|
: locationName // ignore: cast_nullable_to_non_nullable
|
||||||
null == locationName
|
as String,
|
||||||
? _value.locationName
|
duration: null == duration
|
||||||
: locationName // ignore: cast_nullable_to_non_nullable
|
? _value.duration
|
||||||
as String,
|
: duration // ignore: cast_nullable_to_non_nullable
|
||||||
duration:
|
as int,
|
||||||
null == duration
|
timeOfDay: null == timeOfDay
|
||||||
? _value.duration
|
? _value.timeOfDay
|
||||||
: duration // ignore: cast_nullable_to_non_nullable
|
: timeOfDay // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as TimeOfDay,
|
||||||
timeOfDay:
|
familyFriendly: null == familyFriendly
|
||||||
null == timeOfDay
|
? _value.familyFriendly
|
||||||
? _value.timeOfDay
|
: familyFriendly // ignore: cast_nullable_to_non_nullable
|
||||||
: timeOfDay // ignore: cast_nullable_to_non_nullable
|
as bool,
|
||||||
as TimeOfDay,
|
price: null == price
|
||||||
familyFriendly:
|
? _value.price
|
||||||
null == familyFriendly
|
: price // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.familyFriendly
|
as int,
|
||||||
: familyFriendly // ignore: cast_nullable_to_non_nullable
|
destinationRef: null == destinationRef
|
||||||
as bool,
|
? _value.destinationRef
|
||||||
price:
|
: destinationRef // ignore: cast_nullable_to_non_nullable
|
||||||
null == price
|
as String,
|
||||||
? _value.price
|
ref: null == ref
|
||||||
: price // ignore: cast_nullable_to_non_nullable
|
? _value.ref
|
||||||
as int,
|
: ref // ignore: cast_nullable_to_non_nullable
|
||||||
destinationRef:
|
as String,
|
||||||
null == destinationRef
|
imageUrl: null == imageUrl
|
||||||
? _value.destinationRef
|
? _value.imageUrl
|
||||||
: destinationRef // ignore: cast_nullable_to_non_nullable
|
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
ref:
|
|
||||||
null == ref
|
|
||||||
? _value.ref
|
|
||||||
: ref // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
imageUrl:
|
|
||||||
null == imageUrl
|
|
||||||
? _value.imageUrl
|
|
||||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,31 +85,26 @@ class _$BookingCopyWithImpl<$Res, $Val extends Booking>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
id:
|
id: freezed == id
|
||||||
freezed == id
|
? _value.id
|
||||||
? _value.id
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
as int?,
|
||||||
as int?,
|
startDate: null == startDate
|
||||||
startDate:
|
? _value.startDate
|
||||||
null == startDate
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.startDate
|
as DateTime,
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
endDate: null == endDate
|
||||||
as DateTime,
|
? _value.endDate
|
||||||
endDate:
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
null == endDate
|
as DateTime,
|
||||||
? _value.endDate
|
destination: null == destination
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
? _value.destination
|
||||||
as DateTime,
|
: destination // ignore: cast_nullable_to_non_nullable
|
||||||
destination:
|
as Destination,
|
||||||
null == destination
|
activity: null == activity
|
||||||
? _value.destination
|
? _value.activity
|
||||||
: destination // ignore: cast_nullable_to_non_nullable
|
: activity // ignore: cast_nullable_to_non_nullable
|
||||||
as Destination,
|
as List<Activity>,
|
||||||
activity:
|
|
||||||
null == activity
|
|
||||||
? _value.activity
|
|
||||||
: activity // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<Activity>,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -168,31 +163,26 @@ class __$$BookingImplCopyWithImpl<$Res>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$BookingImpl(
|
_$BookingImpl(
|
||||||
id:
|
id: freezed == id
|
||||||
freezed == id
|
? _value.id
|
||||||
? _value.id
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
as int?,
|
||||||
as int?,
|
startDate: null == startDate
|
||||||
startDate:
|
? _value.startDate
|
||||||
null == startDate
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.startDate
|
as DateTime,
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
endDate: null == endDate
|
||||||
as DateTime,
|
? _value.endDate
|
||||||
endDate:
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
null == endDate
|
as DateTime,
|
||||||
? _value.endDate
|
destination: null == destination
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
? _value.destination
|
||||||
as DateTime,
|
: destination // ignore: cast_nullable_to_non_nullable
|
||||||
destination:
|
as Destination,
|
||||||
null == destination
|
activity: null == activity
|
||||||
? _value.destination
|
? _value._activity
|
||||||
: destination // ignore: cast_nullable_to_non_nullable
|
: activity // ignore: cast_nullable_to_non_nullable
|
||||||
as Destination,
|
as List<Activity>,
|
||||||
activity:
|
|
||||||
null == activity
|
|
||||||
? _value._activity
|
|
||||||
: activity // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<Activity>,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ _$BookingImpl _$$BookingImplFromJson(Map<String, dynamic> json) =>
|
|||||||
destination: Destination.fromJson(
|
destination: Destination.fromJson(
|
||||||
json['destination'] as Map<String, dynamic>,
|
json['destination'] as Map<String, dynamic>,
|
||||||
),
|
),
|
||||||
activity:
|
activity: (json['activity'] as List<dynamic>)
|
||||||
(json['activity'] as List<dynamic>)
|
.map((e) => Activity.fromJson(e as Map<String, dynamic>))
|
||||||
.map((e) => Activity.fromJson(e as Map<String, dynamic>))
|
.toList(),
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$BookingImplToJson(_$BookingImpl instance) =>
|
Map<String, dynamic> _$$BookingImplToJson(_$BookingImpl instance) =>
|
||||||
|
|||||||
@@ -75,26 +75,22 @@ class _$BookingSummaryCopyWithImpl<$Res, $Val extends BookingSummary>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
id:
|
id: null == id
|
||||||
null == id
|
? _value.id
|
||||||
? _value.id
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
as int,
|
||||||
as int,
|
name: null == name
|
||||||
name:
|
? _value.name
|
||||||
null == name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.name
|
as String,
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
startDate: null == startDate
|
||||||
as String,
|
? _value.startDate
|
||||||
startDate:
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
null == startDate
|
as DateTime,
|
||||||
? _value.startDate
|
endDate: null == endDate
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
? _value.endDate
|
||||||
as DateTime,
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
endDate:
|
as DateTime,
|
||||||
null == endDate
|
|
||||||
? _value.endDate
|
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -134,26 +130,22 @@ class __$$BookingSummaryImplCopyWithImpl<$Res>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$BookingSummaryImpl(
|
_$BookingSummaryImpl(
|
||||||
id:
|
id: null == id
|
||||||
null == id
|
? _value.id
|
||||||
? _value.id
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
as int,
|
||||||
as int,
|
name: null == name
|
||||||
name:
|
? _value.name
|
||||||
null == name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.name
|
as String,
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
startDate: null == startDate
|
||||||
as String,
|
? _value.startDate
|
||||||
startDate:
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
null == startDate
|
as DateTime,
|
||||||
? _value.startDate
|
endDate: null == endDate
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
? _value.endDate
|
||||||
as DateTime,
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
endDate:
|
as DateTime,
|
||||||
null == endDate
|
|
||||||
? _value.endDate
|
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,16 +62,14 @@ class _$ContinentCopyWithImpl<$Res, $Val extends Continent>
|
|||||||
$Res call({Object? name = null, Object? imageUrl = null}) {
|
$Res call({Object? name = null, Object? imageUrl = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
name:
|
name: null == name
|
||||||
null == name
|
? _value.name
|
||||||
? _value.name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
imageUrl: null == imageUrl
|
||||||
imageUrl:
|
? _value.imageUrl
|
||||||
null == imageUrl
|
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.imageUrl
|
as String,
|
||||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -106,16 +104,14 @@ class __$$ContinentImplCopyWithImpl<$Res>
|
|||||||
$Res call({Object? name = null, Object? imageUrl = null}) {
|
$Res call({Object? name = null, Object? imageUrl = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$ContinentImpl(
|
_$ContinentImpl(
|
||||||
name:
|
name: null == name
|
||||||
null == name
|
? _value.name
|
||||||
? _value.name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
imageUrl: null == imageUrl
|
||||||
imageUrl:
|
? _value.imageUrl
|
||||||
null == imageUrl
|
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.imageUrl
|
as String,
|
||||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,41 +95,34 @@ class _$DestinationCopyWithImpl<$Res, $Val extends Destination>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
ref:
|
ref: null == ref
|
||||||
null == ref
|
? _value.ref
|
||||||
? _value.ref
|
: ref // ignore: cast_nullable_to_non_nullable
|
||||||
: ref // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
name: null == name
|
||||||
name:
|
? _value.name
|
||||||
null == name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.name
|
as String,
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
country: null == country
|
||||||
as String,
|
? _value.country
|
||||||
country:
|
: country // ignore: cast_nullable_to_non_nullable
|
||||||
null == country
|
as String,
|
||||||
? _value.country
|
continent: null == continent
|
||||||
: country // ignore: cast_nullable_to_non_nullable
|
? _value.continent
|
||||||
as String,
|
: continent // ignore: cast_nullable_to_non_nullable
|
||||||
continent:
|
as String,
|
||||||
null == continent
|
knownFor: null == knownFor
|
||||||
? _value.continent
|
? _value.knownFor
|
||||||
: continent // ignore: cast_nullable_to_non_nullable
|
: knownFor // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
knownFor:
|
tags: null == tags
|
||||||
null == knownFor
|
? _value.tags
|
||||||
? _value.knownFor
|
: tags // ignore: cast_nullable_to_non_nullable
|
||||||
: knownFor // ignore: cast_nullable_to_non_nullable
|
as List<String>,
|
||||||
as String,
|
imageUrl: null == imageUrl
|
||||||
tags:
|
? _value.imageUrl
|
||||||
null == tags
|
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.tags
|
as String,
|
||||||
: tags // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>,
|
|
||||||
imageUrl:
|
|
||||||
null == imageUrl
|
|
||||||
? _value.imageUrl
|
|
||||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -180,41 +173,34 @@ class __$$DestinationImplCopyWithImpl<$Res>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$DestinationImpl(
|
_$DestinationImpl(
|
||||||
ref:
|
ref: null == ref
|
||||||
null == ref
|
? _value.ref
|
||||||
? _value.ref
|
: ref // ignore: cast_nullable_to_non_nullable
|
||||||
: ref // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
name: null == name
|
||||||
name:
|
? _value.name
|
||||||
null == name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.name
|
as String,
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
country: null == country
|
||||||
as String,
|
? _value.country
|
||||||
country:
|
: country // ignore: cast_nullable_to_non_nullable
|
||||||
null == country
|
as String,
|
||||||
? _value.country
|
continent: null == continent
|
||||||
: country // ignore: cast_nullable_to_non_nullable
|
? _value.continent
|
||||||
as String,
|
: continent // ignore: cast_nullable_to_non_nullable
|
||||||
continent:
|
as String,
|
||||||
null == continent
|
knownFor: null == knownFor
|
||||||
? _value.continent
|
? _value.knownFor
|
||||||
: continent // ignore: cast_nullable_to_non_nullable
|
: knownFor // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
knownFor:
|
tags: null == tags
|
||||||
null == knownFor
|
? _value._tags
|
||||||
? _value.knownFor
|
: tags // ignore: cast_nullable_to_non_nullable
|
||||||
: knownFor // ignore: cast_nullable_to_non_nullable
|
as List<String>,
|
||||||
as String,
|
imageUrl: null == imageUrl
|
||||||
tags:
|
? _value.imageUrl
|
||||||
null == tags
|
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||||
? _value._tags
|
as String,
|
||||||
: tags // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>,
|
|
||||||
imageUrl:
|
|
||||||
null == imageUrl
|
|
||||||
? _value.imageUrl
|
|
||||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,36 +90,30 @@ class _$ItineraryConfigCopyWithImpl<$Res, $Val extends ItineraryConfig>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
continent:
|
continent: freezed == continent
|
||||||
freezed == continent
|
? _value.continent
|
||||||
? _value.continent
|
: continent // ignore: cast_nullable_to_non_nullable
|
||||||
: continent // ignore: cast_nullable_to_non_nullable
|
as String?,
|
||||||
as String?,
|
startDate: freezed == startDate
|
||||||
startDate:
|
? _value.startDate
|
||||||
freezed == startDate
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.startDate
|
as DateTime?,
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
endDate: freezed == endDate
|
||||||
as DateTime?,
|
? _value.endDate
|
||||||
endDate:
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
freezed == endDate
|
as DateTime?,
|
||||||
? _value.endDate
|
guests: freezed == guests
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
? _value.guests
|
||||||
as DateTime?,
|
: guests // ignore: cast_nullable_to_non_nullable
|
||||||
guests:
|
as int?,
|
||||||
freezed == guests
|
destination: freezed == destination
|
||||||
? _value.guests
|
? _value.destination
|
||||||
: guests // ignore: cast_nullable_to_non_nullable
|
: destination // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,
|
as String?,
|
||||||
destination:
|
activities: null == activities
|
||||||
freezed == destination
|
? _value.activities
|
||||||
? _value.destination
|
: activities // ignore: cast_nullable_to_non_nullable
|
||||||
: destination // ignore: cast_nullable_to_non_nullable
|
as List<String>,
|
||||||
as String?,
|
|
||||||
activities:
|
|
||||||
null == activities
|
|
||||||
? _value.activities
|
|
||||||
: activities // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -168,36 +162,30 @@ class __$$ItineraryConfigImplCopyWithImpl<$Res>
|
|||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$ItineraryConfigImpl(
|
_$ItineraryConfigImpl(
|
||||||
continent:
|
continent: freezed == continent
|
||||||
freezed == continent
|
? _value.continent
|
||||||
? _value.continent
|
: continent // ignore: cast_nullable_to_non_nullable
|
||||||
: continent // ignore: cast_nullable_to_non_nullable
|
as String?,
|
||||||
as String?,
|
startDate: freezed == startDate
|
||||||
startDate:
|
? _value.startDate
|
||||||
freezed == startDate
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.startDate
|
as DateTime?,
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
endDate: freezed == endDate
|
||||||
as DateTime?,
|
? _value.endDate
|
||||||
endDate:
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
freezed == endDate
|
as DateTime?,
|
||||||
? _value.endDate
|
guests: freezed == guests
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
? _value.guests
|
||||||
as DateTime?,
|
: guests // ignore: cast_nullable_to_non_nullable
|
||||||
guests:
|
as int?,
|
||||||
freezed == guests
|
destination: freezed == destination
|
||||||
? _value.guests
|
? _value.destination
|
||||||
: guests // ignore: cast_nullable_to_non_nullable
|
: destination // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,
|
as String?,
|
||||||
destination:
|
activities: null == activities
|
||||||
freezed == destination
|
? _value._activities
|
||||||
? _value.destination
|
: activities // ignore: cast_nullable_to_non_nullable
|
||||||
: destination // ignore: cast_nullable_to_non_nullable
|
as List<String>,
|
||||||
as String?,
|
|
||||||
activities:
|
|
||||||
null == activities
|
|
||||||
? _value._activities
|
|
||||||
: activities // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,12 @@ _$ItineraryConfigImpl _$$ItineraryConfigImplFromJson(
|
|||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
) => _$ItineraryConfigImpl(
|
) => _$ItineraryConfigImpl(
|
||||||
continent: json['continent'] as String?,
|
continent: json['continent'] as String?,
|
||||||
startDate:
|
startDate: json['startDate'] == null
|
||||||
json['startDate'] == null
|
? null
|
||||||
? null
|
: DateTime.parse(json['startDate'] as String),
|
||||||
: DateTime.parse(json['startDate'] as String),
|
endDate: json['endDate'] == null
|
||||||
endDate:
|
? null
|
||||||
json['endDate'] == null
|
: DateTime.parse(json['endDate'] as String),
|
||||||
? null
|
|
||||||
: DateTime.parse(json['endDate'] as String),
|
|
||||||
guests: (json['guests'] as num?)?.toInt(),
|
guests: (json['guests'] as num?)?.toInt(),
|
||||||
destination: json['destination'] as String?,
|
destination: json['destination'] as String?,
|
||||||
activities:
|
activities:
|
||||||
|
|||||||
@@ -61,16 +61,14 @@ class _$UserCopyWithImpl<$Res, $Val extends User>
|
|||||||
$Res call({Object? name = null, Object? picture = null}) {
|
$Res call({Object? name = null, Object? picture = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
name:
|
name: null == name
|
||||||
null == name
|
? _value.name
|
||||||
? _value.name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
picture: null == picture
|
||||||
picture:
|
? _value.picture
|
||||||
null == picture
|
: picture // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.picture
|
as String,
|
||||||
: picture // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -102,16 +100,14 @@ class __$$UserImplCopyWithImpl<$Res>
|
|||||||
$Res call({Object? name = null, Object? picture = null}) {
|
$Res call({Object? name = null, Object? picture = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$UserImpl(
|
_$UserImpl(
|
||||||
name:
|
name: null == name
|
||||||
null == name
|
? _value.name
|
||||||
? _value.name
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
as String,
|
||||||
as String,
|
picture: null == picture
|
||||||
picture:
|
? _value.picture
|
||||||
null == picture
|
: picture // ignore: cast_nullable_to_non_nullable
|
||||||
? _value.picture
|
as String,
|
||||||
: picture // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,12 +63,9 @@ class BookingCreateUseCase {
|
|||||||
return Result.error(activitiesResult.error);
|
return Result.error(activitiesResult.error);
|
||||||
case Ok<List<Activity>>():
|
case Ok<List<Activity>>():
|
||||||
}
|
}
|
||||||
final activities =
|
final activities = activitiesResult.value
|
||||||
activitiesResult.value
|
.where((activity) => itineraryConfig.activities.contains(activity.ref))
|
||||||
.where(
|
.toList();
|
||||||
(activity) => itineraryConfig.activities.contains(activity.ref),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
_log.fine('Activities loaded (${activities.length})');
|
_log.fine('Activities loaded (${activities.length})');
|
||||||
|
|
||||||
// Check if dates are set
|
// Check if dates are set
|
||||||
|
|||||||
@@ -67,26 +67,24 @@ class ActivitiesViewModel extends ChangeNotifier {
|
|||||||
switch (resultActivities) {
|
switch (resultActivities) {
|
||||||
case Ok():
|
case Ok():
|
||||||
{
|
{
|
||||||
_daytimeActivities =
|
_daytimeActivities = resultActivities.value
|
||||||
resultActivities.value
|
.where(
|
||||||
.where(
|
(activity) => [
|
||||||
(activity) => [
|
TimeOfDay.any,
|
||||||
TimeOfDay.any,
|
TimeOfDay.morning,
|
||||||
TimeOfDay.morning,
|
TimeOfDay.afternoon,
|
||||||
TimeOfDay.afternoon,
|
].contains(activity.timeOfDay),
|
||||||
].contains(activity.timeOfDay),
|
)
|
||||||
)
|
.toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
_eveningActivities =
|
_eveningActivities = resultActivities.value
|
||||||
resultActivities.value
|
.where(
|
||||||
.where(
|
(activity) => [
|
||||||
(activity) => [
|
TimeOfDay.evening,
|
||||||
TimeOfDay.evening,
|
TimeOfDay.night,
|
||||||
TimeOfDay.night,
|
].contains(activity.timeOfDay),
|
||||||
].contains(activity.timeOfDay),
|
)
|
||||||
)
|
.toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
_log.fine(
|
_log.fine(
|
||||||
'Activities (daytime: ${_daytimeActivities.length}, '
|
'Activities (daytime: ${_daytimeActivities.length}, '
|
||||||
|
|||||||
@@ -71,10 +71,9 @@ class _ActivitiesScreenState extends State<ActivitiesScreen> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ErrorIndicator(
|
child: ErrorIndicator(
|
||||||
title:
|
title: AppLocalization.of(
|
||||||
AppLocalization.of(
|
context,
|
||||||
context,
|
).errorWhileLoadingActivities,
|
||||||
).errorWhileLoadingActivities,
|
|
||||||
label: AppLocalization.of(context).tryAgain,
|
label: AppLocalization.of(context).tryAgain,
|
||||||
onPressed: widget.viewModel.loadActivities.execute,
|
onPressed: widget.viewModel.loadActivities.execute,
|
||||||
),
|
),
|
||||||
@@ -171,10 +170,9 @@ class _BottomArea extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
key: const Key(confirmButtonKey),
|
key: const Key(confirmButtonKey),
|
||||||
onPressed:
|
onPressed: viewModel.selectedActivities.isNotEmpty
|
||||||
viewModel.selectedActivities.isNotEmpty
|
? viewModel.saveActivities.execute
|
||||||
? viewModel.saveActivities.execute
|
: null,
|
||||||
: null,
|
|
||||||
child: Text(AppLocalization.of(context).confirm),
|
child: Text(AppLocalization.of(context).confirm),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -50,38 +50,40 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Column(
|
body: SingleChildScrollView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
const TiltedCards(),
|
children: [
|
||||||
Padding(
|
const TiltedCards(),
|
||||||
padding: Dimens.of(context).edgeInsetsScreenSymmetric,
|
Padding(
|
||||||
child: Column(
|
padding: Dimens.of(context).edgeInsetsScreenSymmetric,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
TextField(controller: _email),
|
children: [
|
||||||
const SizedBox(height: Dimens.paddingVertical),
|
TextField(controller: _email),
|
||||||
TextField(controller: _password, obscureText: true),
|
const SizedBox(height: Dimens.paddingVertical),
|
||||||
const SizedBox(height: Dimens.paddingVertical),
|
TextField(controller: _password, obscureText: true),
|
||||||
ListenableBuilder(
|
const SizedBox(height: Dimens.paddingVertical),
|
||||||
listenable: widget.viewModel.login,
|
ListenableBuilder(
|
||||||
builder: (context, _) {
|
listenable: widget.viewModel.login,
|
||||||
return FilledButton(
|
builder: (context, _) {
|
||||||
onPressed: () {
|
return FilledButton(
|
||||||
widget.viewModel.login.execute((
|
onPressed: () {
|
||||||
_email.value.text,
|
widget.viewModel.login.execute((
|
||||||
_password.value.text,
|
_email.value.text,
|
||||||
));
|
_password.value.text,
|
||||||
},
|
));
|
||||||
child: Text(AppLocalization.of(context).login),
|
},
|
||||||
);
|
child: Text(AppLocalization.of(context).login),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -99,11 +101,10 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
content: Text(AppLocalization.of(context).errorWhileLogin),
|
content: Text(AppLocalization.of(context).errorWhileLogin),
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: AppLocalization.of(context).tryAgain,
|
label: AppLocalization.of(context).tryAgain,
|
||||||
onPressed:
|
onPressed: () => widget.viewModel.login.execute((
|
||||||
() => widget.viewModel.login.execute((
|
_email.value.text,
|
||||||
_email.value.text,
|
_password.value.text,
|
||||||
_password.value.text,
|
)),
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user