snapshot of https://github.com/flutter/samples.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled
13
.gemini/settings.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"context": {
|
||||||
|
"fileName": "/.prompts/llm.md"
|
||||||
|
},
|
||||||
|
"mcpServers": {
|
||||||
|
"dart": {
|
||||||
|
"command": "dart",
|
||||||
|
"args": [
|
||||||
|
"mcp-server"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
.gemini/settings.json.orig
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"dart": {
|
||||||
|
"command": "dart",
|
||||||
|
"args": [
|
||||||
|
"mcp-server"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contextFileName": "/.prompts/llm.md"
|
||||||
|
}
|
||||||
39
.gitattributes
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# Always perform LF normalization on these files
|
||||||
|
*.c text
|
||||||
|
*.cc text
|
||||||
|
*.cmake text
|
||||||
|
*.dart text
|
||||||
|
*.gradle text
|
||||||
|
*.h text
|
||||||
|
*.html text
|
||||||
|
*.java text
|
||||||
|
*.json text
|
||||||
|
*.md text
|
||||||
|
*.py text
|
||||||
|
*.sh text
|
||||||
|
*.swift text
|
||||||
|
*.txt text
|
||||||
|
*.xml text
|
||||||
|
*.yaml text
|
||||||
|
|
||||||
|
# Make sure that these Windows files always have CRLF line endings in checkout
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.ps1 text eol=crlf
|
||||||
|
*.rc text eol=crlf
|
||||||
|
*.sln text eol=crlf
|
||||||
|
*.props text eol=crlf
|
||||||
|
*.vcxproj text eol=crlf
|
||||||
|
*.vcxproj.filters text eol=crlf
|
||||||
|
# Including templatized versions.
|
||||||
|
*.sln.tmpl text eol=crlf
|
||||||
|
*.props.tmpl text eol=crlf
|
||||||
|
*.vcxproj.tmpl text eol=crlf
|
||||||
|
|
||||||
|
# Never perform LF normalization on these files
|
||||||
|
*.ico binary
|
||||||
|
*.jar binary
|
||||||
|
*.png binary
|
||||||
|
*.zip binary
|
||||||
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
*Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.*
|
||||||
|
|
||||||
|
*List which issues are fixed by this PR. For larger changes, raising an issue first helps
|
||||||
|
reduce redundant work.*
|
||||||
|
|
||||||
|
## Pre-launch Checklist
|
||||||
|
|
||||||
|
- [ ] I read the [Flutter Style Guide] _recently_, and have followed its advice.
|
||||||
|
- [ ] I signed the [CLA].
|
||||||
|
- [ ] I read the [Contributors Guide].
|
||||||
|
- [ ] I have added sample code updates to the [changelog].
|
||||||
|
- [ ] I updated/added relevant documentation (doc comments with `///`).
|
||||||
|
|
||||||
|
|
||||||
|
If you need help, consider asking for advice on the #hackers-devrel channel on [Discord].
|
||||||
|
|
||||||
|
<!-- Links -->
|
||||||
|
[Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md
|
||||||
|
[CLA]: https://cla.developers.google.com/
|
||||||
|
[Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md
|
||||||
|
[Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
|
||||||
|
[changelog]: ../CHANGELOG.md
|
||||||
158
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
version: 2
|
||||||
|
enable-beta-ecosystems: true
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
# Don't auto-submit github actions updates
|
||||||
|
# labels:
|
||||||
|
# - "autosubmit"
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "web_embedding/ng-flutter"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "add_to_app/books/flutter_module_books/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "add_to_app/multiple_flutters/multiple_flutters_module/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "add_to_app/plugin/flutter_module_using_plugin/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "add_to_app/prebuilt_module/flutter_module/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "analysis_defaults/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "android_splash_screen/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "animations/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "background_isolate_channels/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "desktop_photo_search/fluent_ui/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "desktop_photo_search/material/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "form_app/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "google_maps/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "ios_app_clip/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "material_3_demo/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "navigation_and_routing/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "platform_channels/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "platform_design/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "platform_view_swift/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "simple_sdf/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "simple_shader/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "testing_app/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "web_embedding/element_embedding_demo/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "web_embedding/ng-flutter/flutter/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
labels:
|
||||||
|
- "autosubmit"
|
||||||
|
|
||||||
16
.github/no-response.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Configuration for probot-no-response - https://github.com/probot/no-response
|
||||||
|
|
||||||
|
# Number of days of inactivity before an issue is closed for lack of response.
|
||||||
|
daysUntilClose: 14
|
||||||
|
|
||||||
|
# Label requiring a response.
|
||||||
|
responseRequiredLabel: "needs-info"
|
||||||
|
|
||||||
|
# Comment to post when closing an Issue for lack of response.
|
||||||
|
closeComment: >-
|
||||||
|
Without additional information we're not able to resolve this issue,
|
||||||
|
so it will be closed at this time. You're still free to add more info
|
||||||
|
and respond to any questions above, though. We'll reopen the case
|
||||||
|
if you do.
|
||||||
|
|
||||||
|
Thanks for your contribution!
|
||||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||||
|
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6
|
||||||
|
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
@@ -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@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||||
|
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6
|
||||||
|
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
@@ -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@bcd2ba49218906704ab6c1aa796996da409d3eb1' # 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@3d3c42e5aac5ba805825da76410c181273ba90b1' # 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@3d3c42e5aac5ba805825da76410c181273ba90b1' # 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
@@ -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@3d3c42e5aac5ba805825da76410c181273ba90b1' # ratchet:actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 'Generate GitHub App Token'
|
||||||
|
id: 'generate_token'
|
||||||
|
if: |-
|
||||||
|
${{ vars.APP_ID }}
|
||||||
|
uses: 'actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1' # 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@ed597411d8f924073f98dfc5c65a23a2325f34cd'
|
||||||
|
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
@@ -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@3d3c42e5aac5ba805825da76410c181273ba90b1' # ratchet:actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 'Generate GitHub App Token'
|
||||||
|
id: 'generate_token'
|
||||||
|
if: |-
|
||||||
|
${{ vars.APP_ID }}
|
||||||
|
uses: 'actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1' # 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
@@ -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@3d3c42e5aac5ba805825da76410c181273ba90b1' # ratchet:actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 'Generate GitHub App Token'
|
||||||
|
id: 'generate_token'
|
||||||
|
if: |-
|
||||||
|
${{ vars.APP_ID }}
|
||||||
|
uses: 'actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1' # 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@ed597411d8f924073f98dfc5c65a23a2325f34cd'
|
||||||
|
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.'
|
||||||
|
})
|
||||||
38
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Main Branch CI
|
||||||
|
|
||||||
|
# Declare default permissions as read only.
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *" # Every day at midnight
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flutter-tests:
|
||||||
|
name: Test Flutter ${{ matrix.flutter_version }} on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
if: github.repository == 'flutter/samples'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
flutter_version: [stable, beta]
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||||
|
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: '17'
|
||||||
|
- uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046
|
||||||
|
with:
|
||||||
|
channel: ${{ matrix.flutter_version }}
|
||||||
|
- run: flutter pub get && dart tool/ci_script.dart
|
||||||
87
.gitignore
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Visual Studio Code related
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
**/build/
|
||||||
|
|
||||||
|
# Android related
|
||||||
|
**/gradle-wrapper.jar
|
||||||
|
**/android/.gradle
|
||||||
|
**/android/captures/
|
||||||
|
**/android/gradlew
|
||||||
|
**/android/gradlew.bat
|
||||||
|
**/android/local.properties
|
||||||
|
**/android/**/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# iOS/XCode related
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/Flutter/flutter_export_environment.sh
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
**/web/**/lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Service account files
|
||||||
|
svc-keyfile.json
|
||||||
|
|
||||||
|
# Lockfiles
|
||||||
|
Podfile.lock
|
||||||
|
pubspec.lock
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
|
||||||
|
.claude/
|
||||||
|
logs/
|
||||||
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
@@ -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
@@ -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.
|
||||||
8
AUTHORS
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Below is a list of people and organizations that have contributed
|
||||||
|
# to the project. Names should be added to the list like so:
|
||||||
|
#
|
||||||
|
# Name/Organization <email address>
|
||||||
|
|
||||||
|
Google Inc.
|
||||||
|
Abhijeeth Padarthi <rkinabhi@gmail.com>
|
||||||
|
Filip Hracek <filip@raindead.com>
|
||||||
10
CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
Notable changes to the samples in this repository are listed here.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### compass_app
|
||||||
|
|
||||||
|
* Scope `LogoutViewModel` to the home route so it is not recreated on every
|
||||||
|
`HomeHeader` rebuild ([#2604](https://github.com/flutter/samples/issues/2604)).
|
||||||
273
CONTRIBUTING.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Want to contribute to the Flutter sample ecosystem? Great! First, read this
|
||||||
|
page (including the small print at the end).
|
||||||
|
|
||||||
|
Do you work at Google? Great! You should **definitely** read this page before submitting a PR :)
|
||||||
|
|
||||||
|
* [Before you contribute]
|
||||||
|
* [Contribute enhancements and fixes]
|
||||||
|
* [Writing a new sample]
|
||||||
|
* [Sample checklist]
|
||||||
|
|
||||||
|
_See also: [Flutter's code of conduct]_
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
# Before you contribute
|
||||||
|
|
||||||
|
|
||||||
|
## Is this the right place for your sample?
|
||||||
|
|
||||||
|
In most cases, if you've written a great sample app, it should be maintained
|
||||||
|
in your own repository. You can maintain your sample app in your own repo
|
||||||
|
(or with another source control provider) and still be as important a part of
|
||||||
|
the Flutter-verse as anything you see here.
|
||||||
|
|
||||||
|
**What should be contributed here, then?**
|
||||||
|
|
||||||
|
Fixes and necessary improvements to the existing samples, mostly.
|
||||||
|
|
||||||
|
## File an issue first!
|
||||||
|
|
||||||
|
If you see a bug or have an idea for a feature that you feel would improve one
|
||||||
|
of the samples already in the repo, **please [file an issue] before you begin
|
||||||
|
coding or send a PR**. This will help prevent duplicate work by letting us know
|
||||||
|
what you're up to. It will help avoid a situation in which you spend a lot of
|
||||||
|
time coding something that's not quite right for the repo or its goals.
|
||||||
|
|
||||||
|
## Sign the license agreement.
|
||||||
|
|
||||||
|
Before we can use your code, you must sign the
|
||||||
|
[Google Individual Contributor License Agreement]
|
||||||
|
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||||
|
copyright to your changes, even after your contribution becomes part of our
|
||||||
|
codebase, so we need your permission to use and distribute your code. We also
|
||||||
|
need to be sure of various other things—for instance that you'll tell us if you
|
||||||
|
know that your code infringes on other people's patents. You don't have to sign
|
||||||
|
the CLA until after you've submitted your code for review and a member has
|
||||||
|
approved it, but you must do it before we can put your code into our codebase.
|
||||||
|
|
||||||
|
Before you start working on a larger contribution, you should get in touch with
|
||||||
|
us first through the issue tracker with your idea so that we can help out and
|
||||||
|
possibly guide you. Coordinating up front makes it much easier to avoid
|
||||||
|
frustration later on.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
# Contribute enhancements and fixes
|
||||||
|
|
||||||
|
Enhancements and bug fixes are welcome and appreciated.
|
||||||
|
If you spot an [issue] that you might want to fix but aren't sure how to get started,
|
||||||
|
feel free to comment on the issue and tag @ericwindmill with questions.
|
||||||
|
|
||||||
|
## Filing issues and evaluating existing samples
|
||||||
|
|
||||||
|
You can also contribute by filing issues against existing samples. This is thankless work
|
||||||
|
and is _greatly_ appreciated. It's also a good way to familiarize yourself with the repository.
|
||||||
|
|
||||||
|
To evaluate a sample for possible issues, use the [Sample checklist] below.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
# Writing a new sample
|
||||||
|
|
||||||
|
Before filing an issue for a new sample or submitting a PR, you must read the rest of this page.
|
||||||
|
|
||||||
|
## What is a sample?
|
||||||
|
|
||||||
|
Samples are reference materials that teach developers how to solve a specific problem.
|
||||||
|
Samples contain **correct and concise code** that developers can
|
||||||
|
**quickly understand** and **easily reuse** with minimal side effects.
|
||||||
|
|
||||||
|
### What samples should we have?
|
||||||
|
|
||||||
|
Samples in this repo demonstrates how to **use current features** of the **Flutter and Dart SDKs** using **best practices**.
|
||||||
|
In other words, a sample should meet the following criteria:
|
||||||
|
|
||||||
|
* Demonstrates _how to use the SDKS_ rather than show an end-product.
|
||||||
|
* Covers a critical developer journey in the SDK that is useful to >80% of developers.
|
||||||
|
* Prefers primitives in the SDK over libraries.
|
||||||
|
* Demonstrates features available on the stable channel of Flutter.
|
||||||
|
|
||||||
|
These guidelines can be broken in rare cases. For example, this is likely the best place in the
|
||||||
|
Flutter github org for demo apps, but those demo apps must be justified.
|
||||||
|
|
||||||
|
## Is this the right place for your sample? (revisited)
|
||||||
|
|
||||||
|
Validate your idea against the following criteria:
|
||||||
|
|
||||||
|
1. **The sample solves a single problem or set of tightly coupled problems.** <br/>
|
||||||
|
If not, you're either writing a Demo app or an extended sample. These might
|
||||||
|
still be appropriate for the sample repos, but need to be justified.
|
||||||
|
|
||||||
|
2. **The target audience for the sample isn’t beginners.**
|
||||||
|
3. **The sample code isn’t so complex that it needs video or text explanation.** <br />
|
||||||
|
Samples are reference materials, and the code should speak for itself. If either of these aren't true,
|
||||||
|
you may prefer to write a blogpost or tutorial.
|
||||||
|
|
||||||
|
5. **The sample isn’t tied to an event.** <br />
|
||||||
|
If it is, you should likely use a personal repository.
|
||||||
|
|
||||||
|
5. **The sample isn’t tied to a library or package.** <br />
|
||||||
|
If it is, the usage example should likely be in the [repository of that package].
|
||||||
|
|
||||||
|
6. **The topic doesn’t have crossover with any existing samples.** <br />
|
||||||
|
If it does, ensure that it wouldn't be better to update the existing sample.
|
||||||
|
|
||||||
|
## What are your ongoing maintenance responsibilities?
|
||||||
|
|
||||||
|
All sample code has an unbound maintenance cost. If you cannot commit to maintaining a sample
|
||||||
|
(for the foreseeable future, barring changes in life circumstances), then you should talk to the
|
||||||
|
[primary maintainers] of this project, particularly @ericwindmill, before submitting a PR.
|
||||||
|
|
||||||
|
Any new sample **must** have a CODEOWNER, and that owner is most likely you (the author).
|
||||||
|
|
||||||
|
## What type of sample are your writing?
|
||||||
|
|
||||||
|
This repository contains two types of sample apps: **quickstarts** and **demo apps**.
|
||||||
|
|
||||||
|
### Quickstarts
|
||||||
|
|
||||||
|
* **Purpose**: Provides starting points that a developer can extend.
|
||||||
|
Solves specific problems or implements generic app features.
|
||||||
|
* **Size**: <= 200 lines of code, not including boiler plate (guide, not rule)
|
||||||
|
* **Qualities:**
|
||||||
|
* Contains one feature or a small set of related features, but generally runnable.
|
||||||
|
* Generic and un-opinionated about the context in which it is used.
|
||||||
|
* Prioritizes being generic over best practices.
|
||||||
|
* The code is Copy+paste-able.
|
||||||
|
* In Flutter, this means that the code that is being demonstrated should be separated from and boiler plate (i.e. `runApp`)
|
||||||
|
* **Audience:**
|
||||||
|
* Developers with Flutter and Dart experience that don't need code explained to them.
|
||||||
|
* **Answers the questions:**
|
||||||
|
* “What’s the minimal amount of code needed to implement this feature…”
|
||||||
|
* “... so I can understand how it works?”
|
||||||
|
* “... so I can understand how to add it to my app?”
|
||||||
|
* “How can I make sure I know how to extend this feature without missing anything?”
|
||||||
|
* "What code should I use as a starting point given I want to solve a very similar problem?"
|
||||||
|
|
||||||
|
### Demo apps
|
||||||
|
|
||||||
|
* **Purpose**: Meant to be built and ran. Demos the _product_, not how to write code.
|
||||||
|
* **Criteria**: Demo apps _only_ belong here if they...
|
||||||
|
* ...aren't tied to an event or moment in time.
|
||||||
|
* ...aren't tied to another resource (website, workshop, etc).
|
||||||
|
* ...don't require explanation (other than a short README).
|
||||||
|
* ...aren't primarily used to demonstrate an SDK feature.
|
||||||
|
|
||||||
|
### Other sample types
|
||||||
|
|
||||||
|
Other sample types, like demo apps that accompany docs or events, do not belong in this repository.
|
||||||
|
They should be maintained alongside the accompanying resources (i.e. in the website repository), or in a personal repository.
|
||||||
|
|
||||||
|
### The `experimental` folder
|
||||||
|
|
||||||
|
The experimental folder is being deprecated. If you have an experiemental sample project,
|
||||||
|
you should manage it in a personal repository until it runs on the stable channel.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
# Sample checklist
|
||||||
|
|
||||||
|
Every piece of code will have value to someone, but it is easy for maintenance costs to grow over time.
|
||||||
|
The more items checked on this list, the higher the value and lower the maintenance costs.
|
||||||
|
|
||||||
|
Use this checklist to write new samples and evaluate existing ones.
|
||||||
|
|
||||||
|
**NB:** Demo apps have looser requirements. Use your best judgement when following the guidelines.
|
||||||
|
|
||||||
|
* Code
|
||||||
|
- [ ] Be designed to build against the current [stable]
|
||||||
|
release of the Flutter SDK.
|
||||||
|
- [ ] Sample is the only one for the API in question (across all Dash samples)
|
||||||
|
- [ ] Repository and pubspec name are titled for its ingredients (i.e. mvvm_architecture instead of compass_app)
|
||||||
|
- [ ] Only includes the minimal required code to demonstrate the feature or API and run the code.
|
||||||
|
- [ ] Sample favors 1P dependencies over 3P.
|
||||||
|
- [ ] Code has been sufficiently explained with doc comments.
|
||||||
|
- [ ] The sample app separates its bespoke code from general Flutter or Dart code.
|
||||||
|
- [ ] Favor readability over best practices unless readability requires an anti pattern. Use your best judgment.
|
||||||
|
- [ ] Avoid adding an onerous amount of blobs (typically images or other assets) to the repo.
|
||||||
|
- [ ] All files in the project must start with the appropriate [file headers].
|
||||||
|
|
||||||
|
* Tests, style and maintenance
|
||||||
|
- [ ] Sample has Dart tests that cover the business logic
|
||||||
|
- [ ] Sample is wired into the list of projects in the CI scripts for [stable](tool/flutter_ci_script_stable.sh),
|
||||||
|
[beta](tool/flutter_ci_script_beta.sh), and [master](tool/flutter_ci_script_master.sh),
|
||||||
|
which runs the analyzer, the formatter, and `flutter test`.
|
||||||
|
- [ ] Sample has a codeowner (it's likely you, the author)
|
||||||
|
- [ ] Sample is formatted with `dart format`.
|
||||||
|
- [ ] Sample has no analyzer errors or warnings.
|
||||||
|
- [ ] Add the new project directory to the [dependabot](.github/dependabot.yaml) configuration
|
||||||
|
to keep dependencies updated in an on-going basis.
|
||||||
|
- [ ] Follows the [Flutter style guide]
|
||||||
|
- [ ] Include the top level [`analysis_options.yaml`](analysis_options.yaml)
|
||||||
|
file used throughout the repo. This file include a base set of analyzer
|
||||||
|
conventions and lints.
|
||||||
|
|
||||||
|
* README
|
||||||
|
- [ ] Describes the apps design and purpose.
|
||||||
|
- [ ] Describes common errors and debugging steps.
|
||||||
|
- [ ] Describes any steps necessary to build and run.
|
||||||
|
- [ ] Has ‘Open in IDX’ button, and a nix file (if compatible with IDX).
|
||||||
|
|
||||||
|
## File headers
|
||||||
|
|
||||||
|
All files in the project must start with the following header.
|
||||||
|
|
||||||
|
```
|
||||||
|
// Copyright 2024 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
```
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
# Code reviews
|
||||||
|
|
||||||
|
All submissions, including submissions by project members, require review.
|
||||||
|
|
||||||
|
This repo is part of the [Flutter](https://github.com/flutter) GitHub account,
|
||||||
|
which means that a lot of folks have the ability to push and merge code. The
|
||||||
|
primary maintainers, though, are:
|
||||||
|
|
||||||
|
* [@RedBrogdon](https://github.com/RedBrogdon)
|
||||||
|
* [@johnpryan](https://github.com/johnpryan)
|
||||||
|
* [@domesticmouse](https://github.com/domesticmouse)
|
||||||
|
* [@ericwindmill](https://github.com/ericwindmill)
|
||||||
|
|
||||||
|
You are free to add one of these folks (particularly @ericwindmill) as a reviewer
|
||||||
|
to any PR sent to this repo. We're happy to comment, answer (or ask) questions,
|
||||||
|
and provide feedback.
|
||||||
|
|
||||||
|
If you're part of a team that's already landed a sample in the repo (Hi,
|
||||||
|
Material!), and you're updating or fixing that sample, you are *not* expected to
|
||||||
|
wait on one of the above folks before merging the code. Have it reviewed by
|
||||||
|
someone you trust on your own team, and then merge it.
|
||||||
|
|
||||||
|
However, if you're adding a new sample, updating a sample you've never worked on
|
||||||
|
before, or changing something that's a meta-concern like the CI setup, web
|
||||||
|
hosting, project setup, etc., please include one of the primary maintainers as a
|
||||||
|
reviewer.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
# The small print
|
||||||
|
|
||||||
|
Contributions made by corporations are covered by a different agreement than the
|
||||||
|
one above, the [Software Grant and Corporate Contributor License Agreement].
|
||||||
|
|
||||||
|
[Flutter's code of conduct]: https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md
|
||||||
|
[Before you contribute]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#before-you-contribute
|
||||||
|
[Contribute enhancements and fixes]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#contribute-enhancements-and-fixes
|
||||||
|
[Writing a new sample]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#writing-a-new-sample
|
||||||
|
[Sample checklist]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#samples-checklist
|
||||||
|
[file headers]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#file-headers
|
||||||
|
[Software Grant and Corporate Contributor License Agreement]: https://developers.google.com/open-source/cla/corporate
|
||||||
|
[issue]: https://github.com/flutter/samples/issues
|
||||||
|
[file an issue]: https://github.com/flutter/samples/issues/new
|
||||||
|
[repository of that package]: https://dart.dev/tools/pub/package-layout#examples
|
||||||
|
[stable]: https://github.com/flutter/flutter/blob/master/docs/releases/Flutter-build-release-channels.md
|
||||||
|
[Flutter style guide]: (https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md)
|
||||||
|
[Google Individual Contributor License Agreement]: https://cla.developers.google.com/about/google-individual
|
||||||
|
[primary maintainers]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#code-reviews
|
||||||
326
LICENSE
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2014, Vaibhav Singh (design) and Rosetta Type Foundry s.r.o. (post-production).
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
17
PATENTS
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
Google hereby grants to you a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this
|
||||||
|
section) patent license to make, have made, use, offer to sell, sell,
|
||||||
|
import, transfer, and otherwise run, modify and propagate the contents
|
||||||
|
of this implementation, where such license applies only to those
|
||||||
|
patent claims, both currently owned by Google and acquired in the
|
||||||
|
future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute
|
||||||
|
or order or agree to the institution of patent litigation or any other
|
||||||
|
patent enforcement activity against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that this
|
||||||
|
implementation constitutes direct or contributory patent infringement,
|
||||||
|
or inducement of patent infringement, then any patent rights granted
|
||||||
|
to you under this License for this implementation shall terminate as
|
||||||
|
of the date such litigation is filed.
|
||||||
167
README.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Flutter samples
|
||||||
|
|
||||||
|
[](https://github.com/flutter/samples/actions?workflow=Main%20Branch%20CI)
|
||||||
|
|
||||||
|
A collection of open source samples that illustrate best practices for
|
||||||
|
[Flutter].
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Googler's, you can freely add samples to the [flutter/demos] repository.
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
### Quickstarts
|
||||||
|
|
||||||
|
* [`asset_transformation`] - Demonstrates how to transform images' color scales and formats.
|
||||||
|
* [`background_isolate_channels`] - Demonstrates how to use long-lived isolates.
|
||||||
|
* [`cupertino_gallery`] - A gallery of Cupertino widgets, demonstrating iOS-style UI components in Flutter.
|
||||||
|
* [`date_planner`] - An in-progress exploration of an iOS-style Date Planner app, similar to the SwiftUI Tutorial app.
|
||||||
|
* [`desktop_photo_search`] - Demonstrates desktop features in both Material and FluentUI design systems.
|
||||||
|
* [`dynamic_theme`] - A developer sample demonstrating how to call on-device Flutter APIs based on output from the Gemini API.
|
||||||
|
* [`form_app`] - A sample demonstrating different types of forms and best practices.
|
||||||
|
* [`google_maps`] - Demonstrates the Google Maps for Flutter plugin.
|
||||||
|
* [`navigation_and_routing`] - A sample that shows how to use [go_router] API to handle common navigation scenarios.
|
||||||
|
* [`pedometer`] - A demo of a plugin that leverages FFIgen & JNIgen to call platform APIs directly from Dart code.
|
||||||
|
* [`platform_design`] - This sample project shows a Flutter app that maximizes application code reuse while adhering to different design patterns on Android and iOS.
|
||||||
|
* [`simple_sdf`] - A simple [Flutter fragment shaders] sample project showing how to draw Signed Distance Functions with the FragmentShader API.
|
||||||
|
* [`simple_shader`] - A simple [Flutter fragment shaders] sample project.
|
||||||
|
* [`testing_app`] - A sample app that shows different types of testing in Flutter.
|
||||||
|
* [`web_embedding`] - This directory contains examples of how to embed Flutter in web apps (without iframes).
|
||||||
|
* [`element_embedding_demo`] - Modifies the index.html of a flutter app so it is launched in a custom hostElement. This is the most basic embedding example.
|
||||||
|
* [`ng-flutter`] - A simple Angular app (and component) that replicates the above example, but in an Angular style.
|
||||||
|
|
||||||
|
### Native platform samples
|
||||||
|
|
||||||
|
* [`add-to-app`] - Collection of samples that demonstrate embedding Flutter a view into a native app.
|
||||||
|
* [`fullscreen`] — Embeds a full screen instance of
|
||||||
|
Flutter into an existing iOS or Android app.
|
||||||
|
* [`prebuilt_module`] — Embeds a full screen
|
||||||
|
instance of Flutter as a prebuilt library that can be loaded into an existing
|
||||||
|
iOS or Android app.
|
||||||
|
* [`plugin`] — Embeds a full screen Flutter instance that
|
||||||
|
is using plugins into an existing iOS or Android app.
|
||||||
|
* [`books`] — Mimics a real world use-case of embedding Flutter into an
|
||||||
|
existing Android app and demonstrates using [Pigeon] to communicate between Flutter and
|
||||||
|
the host application.
|
||||||
|
* [`multiple_flutters`] — Shows the usage of the Flutter
|
||||||
|
Engine Group APIs to embed multiple instances of Flutter into an existing app
|
||||||
|
with low memory cost.
|
||||||
|
* [`android_view`] — Shows how to integrate a Flutter
|
||||||
|
add-to-app module at a view level for Android.
|
||||||
|
* [`android_splash_screen`]
|
||||||
|
* [`ios_app_clip`]
|
||||||
|
* [`platform_channels`] - A sample app which demonstrates how to use MethodChannel, EventChannel, BasicMessageChannel and MessageCodec in Flutter.
|
||||||
|
* [`platform_view_swift`] - A Flutter sample app that combines a native iOS UIViewController with a full-screen Flutter view.
|
||||||
|
|
||||||
|
### Demo galleries
|
||||||
|
|
||||||
|
* [`animations`] - Showcases Flutter's animation features
|
||||||
|
* [`material_3_demo`] - showcases [Material 3] features in the Flutter Material library.
|
||||||
|
|
||||||
|
### Demo apps
|
||||||
|
|
||||||
|
* [`compass_app`] - A sample application that implements MVVM architecture.
|
||||||
|
* [`veggie_seasons`] - A demo application.
|
||||||
|
|
||||||
|
## Flutter sample code
|
||||||
|
|
||||||
|
Samples are **correct and concise code** that developers
|
||||||
|
can **quickly understand** and **easily reuse** with minimal side effects.
|
||||||
|
Samples teach developers how to be successful using Flutter and Dart.
|
||||||
|
They are maintained on an ongoing basis
|
||||||
|
to reflect changing APIs and best practices.
|
||||||
|
|
||||||
|
### Types of samples
|
||||||
|
|
||||||
|
There are two types of sample code in this repository:
|
||||||
|
|
||||||
|
* **Quickstarts** provide a starting point to extend. They answer the question,
|
||||||
|
"What is the minimal amount of code needed to implement this feature?"
|
||||||
|
* **Demo apps** are meant to be built and ran. They demo the _product_,
|
||||||
|
not how to write code.
|
||||||
|
|
||||||
|
A majority of samples in this repository are quickstarts.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Every sample in this repo is fully runnable. To run an example,
|
||||||
|
use `flutter run` inside that example's directory.
|
||||||
|
See the [getting started guide] to install the `flutter` tool.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> If you want to run an add-to-app sample, there are additional requirements.
|
||||||
|
> We suggest reading the [add-to-app documentation].
|
||||||
|
|
||||||
|
## Interested in contributing?
|
||||||
|
|
||||||
|
See the [contributor's guide]!
|
||||||
|
|
||||||
|
## Questions or issues?
|
||||||
|
|
||||||
|
If you have a general question about one of these samples or how to adapt its
|
||||||
|
techniques for one of your own apps, try one of these resources:
|
||||||
|
|
||||||
|
* [The FlutterDev Discord]
|
||||||
|
* [The Flutter Community forum]
|
||||||
|
|
||||||
|
If you run into a bug in one of the samples, please file an issue in the
|
||||||
|
[`flutter/samples` issue tracker].
|
||||||
|
|
||||||
|
|
||||||
|
[`asset_transformation`]: ./asset_transformation
|
||||||
|
[`background_isolate_channels`]: ./background_isolate_channels
|
||||||
|
[`cupertino_gallery`]: ./cupertino_gallery
|
||||||
|
[`date_planner`]: ./date_planner
|
||||||
|
[`desktop_photo_search`]: ./desktop_photo_search
|
||||||
|
[`dynamic_theme`]: ./dynamic_theme
|
||||||
|
[`form_app`]: ./form_app
|
||||||
|
[`google_maps`]: ./google_maps
|
||||||
|
[`navigation_and_routing`]: ./navigation_and_routing
|
||||||
|
[`pedometer`]: ./pedometer
|
||||||
|
[`platform_design`]: ./platform_design
|
||||||
|
[`simple_sdf`]: ./simple_sdf
|
||||||
|
[`simple_shader`]: ./simple_shader
|
||||||
|
[`testing_app`]: ./testing_app
|
||||||
|
[`web_embedding`]: ./web_embedding
|
||||||
|
[`element_embedding_demo`]: ./web_embedding/element_embedding_demo
|
||||||
|
[`ng-flutter`]: ./web_embedding/ng-flutter
|
||||||
|
[`add-to-app`]: ./add_to_app
|
||||||
|
[`fullscreen`]: ./add_to_app/fullscreen
|
||||||
|
[`prebuilt_module`]: ./add_to_app/prebuilt_module
|
||||||
|
[`plugin`]: ./add_to_app/plugin
|
||||||
|
[`books`]: ./add_to_app/books
|
||||||
|
[`multiple_flutters`]: ./add_to_app/multiple_flutters
|
||||||
|
[`android_view`]: ./add_to_app/android_view
|
||||||
|
[`android_splash_screen`]: ./android_splash_screen
|
||||||
|
[`ios_app_clip`]: ./ios_app_clip
|
||||||
|
[`platform_channels`]: ./platform_channels
|
||||||
|
[`platform_view_swift`]: ./platform_view_swift
|
||||||
|
[`animations`]: ./animations
|
||||||
|
[`material_3_demo`]: ./material_3_demo
|
||||||
|
[`compass_app`]: ./compass_app
|
||||||
|
[`veggie_seasons`]: ./veggieseasons
|
||||||
|
|
||||||
|
[Flutter]: https://flutter.dev
|
||||||
|
[getting started guide]: https://docs.flutter.dev/get-started/install
|
||||||
|
[add-to-app documentation]: https://docs.flutter.dev/add-to-app
|
||||||
|
[isolates]: https://api.dart.dev/dart-isolate/Isolate-class.html
|
||||||
|
[Material 3]: https://m3.material.io
|
||||||
|
[go_router]: https://pub.dev/packages/go_router
|
||||||
|
[google_maps_flutter plugin]: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter
|
||||||
|
[package:provider]: https://pub.dev/packages/provider
|
||||||
|
[Flutter fragment shaders]: https://docs.flutter.dev/development/ui/advanced/shaders
|
||||||
|
[TextEditingDeltas]: https://api.flutter.dev/flutter/services/TextEditingDelta-class.html
|
||||||
|
[Pigeon]: https://pub.dev/packages/pigeon
|
||||||
|
[`package:shelf`]: https://pub.dev/packages/shelf
|
||||||
|
[svn]: https://subversion.apache.org/
|
||||||
|
[partial clone]: https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
|
||||||
|
[contributor's guide]: CONTRIBUTING.md
|
||||||
|
[The FlutterDev Discord]: https://discord.gg/rflutterdev
|
||||||
|
[The Flutter Community forum]: https://forum.itsallwidgets.com/latest
|
||||||
|
[`flutter/samples` issue tracker]: https://github.com/flutter/samples/issues
|
||||||
|
[flutter/demos]: https://github.com/flutter/demos
|
||||||
79
add_to_app/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Add-to-App Samples
|
||||||
|
|
||||||
|
This directory contains Android and iOS projects that import and use a Flutter
|
||||||
|
module. They're designed to show recommended approaches for [adding Flutter to
|
||||||
|
existing Android and iOS apps](https://docs.flutter.dev/add-to-app).
|
||||||
|
|
||||||
|
## Samples Listing
|
||||||
|
|
||||||
|
* [`fullscreen`](./fullscreen) — Embeds a full screen instance of
|
||||||
|
Flutter into an existing iOS or Android app.
|
||||||
|
* [`prebuilt_module`](./prebuilt_module) — Embeds a full screen
|
||||||
|
instance of Flutter as a prebuilt library that can be loaded into an existing
|
||||||
|
iOS or Android app.
|
||||||
|
* [`plugin`](./plugin) — Embeds a full screen Flutter instance that
|
||||||
|
is using plugins into an existing iOS or Android app.
|
||||||
|
* [`books`](./books) — Mimics a real world use-case of embedding Flutter into an
|
||||||
|
existing Android app and demonstrates using
|
||||||
|
[Pigeon](https://pub.dev/packages/pigeon) to communicate between Flutter and
|
||||||
|
the host application.
|
||||||
|
* [`multiple_flutters`](./multiple_flutters) — Shows the usage of the Flutter
|
||||||
|
Engine Group APIs to embed multiple instances of Flutter into an existing app
|
||||||
|
with low memory cost.
|
||||||
|
* [`android_view`](./android_view) — Shows how to integrate a Flutter
|
||||||
|
add-to-app module at a view level for Android.
|
||||||
|
|
||||||
|
## Goals for these samples
|
||||||
|
|
||||||
|
* Show developers how to add Flutter to their existing applications.
|
||||||
|
* Show the following options:
|
||||||
|
* Whether to build the Flutter module from source each time the app builds or
|
||||||
|
rely on a separately pre-built module.
|
||||||
|
* Whether plugins are needed by the Flutter module used in the app.
|
||||||
|
* Show Flutter being integrated ergonomically with applications with existing
|
||||||
|
middleware and business logic data classes.
|
||||||
|
|
||||||
|
## Installing Cocoapods
|
||||||
|
|
||||||
|
The iOS samples in this repo require the latest version of Cocoapods. To make
|
||||||
|
sure you've got it, run the following command on a macOS machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo gem install cocoapods
|
||||||
|
```
|
||||||
|
|
||||||
|
See https://guides.cocoapods.org/using/getting-started.html for more details.
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
You can `flutter attach` to the running host application to [debug the Flutter
|
||||||
|
module](https://docs.flutter.dev/add-to-app/debugging). This will
|
||||||
|
allow you to hot reload, set breakpoints, and use DevTools and other debugging
|
||||||
|
functionality, similar to a full Flutter app.
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
If you run into an issue with the sample itself, please file an issue
|
||||||
|
in the [Flutter samples repo](https://github.com/flutter/samples/issues).
|
||||||
|
|
||||||
|
|
||||||
|
## Updating Android samples and its dependencies
|
||||||
|
|
||||||
|
1. Open the top level build.gradle file in Android Studio
|
||||||
|
2. Open “Upgrade Assistant” and click upgrade
|
||||||
|
|
||||||
|
### Troubleshooting Android updates
|
||||||
|
|
||||||
|
* If after upgrading it fails to build, try upgrading to a slightly less new version
|
||||||
|
* If there's an "Unknown class version exception <VERSION NUMBER>" try setting the version of Java used by modifying JAVA_HOME
|
||||||
|
* If it still fails to build, check that the Flutter code referenced in the build is up-to-date and doesn't use discontinued plugins. (Common cases include “namespace”)
|
||||||
|
* The 'project structure' -> 'modules' view can be helpful in understanding the dependency tree.
|
||||||
|
* Once the app builds with the latest gradle/agp, update any deprecated usages in app/build.gradle
|
||||||
|
|
||||||
|
* compileSdkVersion -> sdkVersion
|
||||||
|
* Update the target sdk version and read through each target sdk update
|
||||||
|
* Export broadcast receivers
|
||||||
|
* Update the way flutter is imported to use flutter gradle plugin
|
||||||
|
* https://flutter.dev/go/flutter-gradle-plugin-apply
|
||||||
|
|
||||||
|
* **When updating an app that uses AAR as a Flutter module** -- In android studio update any android dependencies that are indicated as in yellow as old. Keep `androidx.test:runner`, `androidx.test.espresso:espresso-core`, and `androidx.test:core`, as defined in https://github.com/flutter/packages/tree/main/packages/espresso
|
||||||
31
add_to_app/android_view/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# android_view
|
||||||
|
|
||||||
|
An example of an Android app that integrates a Flutter add-to-app module at a
|
||||||
|
view level.
|
||||||
|
|
||||||
|
## tl;dr
|
||||||
|
|
||||||
|
If you're just looking to get up and running quickly, these bash commands will
|
||||||
|
fetch packages and set up dependencies (note that the above commands assume
|
||||||
|
you're building for both iOS and Android, with both toolchains installed):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd flutter_module_using_plugin/
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
# For Android builds:
|
||||||
|
open -a "Android Studio" ../android_view # macOS only
|
||||||
|
# Or open the ../android_fullscreen folder in Android Studio for other platforms.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
For more information about Flutter, check out
|
||||||
|
[flutter.dev](https://flutter.dev).
|
||||||
|
|
||||||
|
For instructions on how to integrate Flutter modules into your existing
|
||||||
|
applications, see Flutter's
|
||||||
|
[add-to-app documentation](https://flutter.dev/docs/development/add-to-app).
|
||||||
15
add_to_app/android_view/android_view/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
||||||
4
add_to_app/android_view/android_view/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# android_view
|
||||||
|
|
||||||
|
An example of an Android app that integrates a Flutter add-to-app module at a
|
||||||
|
view level. For more information see [../README.md](../README.md).
|
||||||
1
add_to_app/android_view/android_view/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
58
add_to_app/android_view/android_view/app/build.gradle
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'kotlin-android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 36
|
||||||
|
|
||||||
|
lint {
|
||||||
|
baseline = file("lint-baseline.xml")
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "dev.flutter.example.androidView"
|
||||||
|
minSdkVersion 24
|
||||||
|
targetSdk 36
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
namespace 'dev.flutter.example.androidView'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
implementation 'androidx.core:core-ktx:1.13.1'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.vectordrawable:vectordrawable:1.2.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
|
||||||
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
||||||
|
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
||||||
|
implementation "androidx.recyclerview:recyclerview:1.3.2"
|
||||||
|
implementation project(path: ':flutter')
|
||||||
|
testImplementation 'junit:junit:4.+'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
|
}
|
||||||
279
add_to_app/android_view/android_view/app/lint-baseline.xml
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<issues format="6" by="lint 7.4.2" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.2)" variant="all" version="7.4.2">
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="RedundantLabel"
|
||||||
|
message="Redundant label can be removed"
|
||||||
|
errorLine1=" android:label="@string/app_name">"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/AndroidManifest.xml"
|
||||||
|
line="14"
|
||||||
|
column="13"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of org.jetbrains.kotlin:kotlin-stdlib than 1.8.22 is available: 1.9.20"
|
||||||
|
errorLine1="}"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="39"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.core:core-ktx than 1.3.2 is available: 1.13.1"
|
||||||
|
errorLine1=""
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="40"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.appcompat:appcompat than 1.2.0 is available: 1.7.0"
|
||||||
|
errorLine1="dependencies {"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="41"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of com.google.android.material:material than 1.2.1 is available: 1.12.0"
|
||||||
|
errorLine1=" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version""
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="42"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.constraintlayout:constraintlayout than 2.0.4 is available: 2.1.4"
|
||||||
|
errorLine1=" implementation 'androidx.core:core-ktx:1.3.2'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="43"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.vectordrawable:vectordrawable than 1.1.0 is available: 1.2.0"
|
||||||
|
errorLine1=" implementation 'androidx.appcompat:appcompat:1.2.0'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="44"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.lifecycle:lifecycle-livedata-ktx than 2.2.0 is available: 2.8.2"
|
||||||
|
errorLine1=" implementation 'com.google.android.material:material:1.2.1'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="45"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.lifecycle:lifecycle-viewmodel-ktx than 2.2.0 is available: 2.8.2"
|
||||||
|
errorLine1=" implementation 'androidx.constraintlayout:constraintlayout:2.0.4'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="46"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.navigation:navigation-fragment-ktx than 2.3.2 is available: 2.7.7"
|
||||||
|
errorLine1=" implementation 'androidx.vectordrawable:vectordrawable:1.1.0'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="47"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.navigation:navigation-ui-ktx than 2.3.2 is available: 2.7.7"
|
||||||
|
errorLine1=" implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="48"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.recyclerview:recyclerview than 1.1.0 is available: 1.3.2"
|
||||||
|
errorLine1=" implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="49"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.test.ext:junit than 1.1.2 is available: 1.1.5"
|
||||||
|
errorLine1=" implementation "androidx.recyclerview:recyclerview:1.1.0""
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="52"
|
||||||
|
column="31"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.test.espresso:espresso-core than 3.3.0 is available: 3.5.1"
|
||||||
|
errorLine1=" implementation project(path: ':flutter')"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="53"
|
||||||
|
column="31"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDynamicVersion"
|
||||||
|
message="Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (junit:junit:4.+)"
|
||||||
|
errorLine1=" implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="51"
|
||||||
|
column="24"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="ExpiredTargetSdkVersion"
|
||||||
|
message="Google Play requires that apps target API level 31 or higher.
"
|
||||||
|
errorLine1=" defaultConfig {"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="12"
|
||||||
|
column="9"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="ObsoleteSdkInt"
|
||||||
|
message="Unnecessary; SDK_INT is always >= 21"
|
||||||
|
errorLine1=" <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/res/values-night/themes.xml"
|
||||||
|
line="13"
|
||||||
|
column="45"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="ObsoleteSdkInt"
|
||||||
|
message="Unnecessary; SDK_INT is always >= 21"
|
||||||
|
errorLine1=" <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/res/values/themes.xml"
|
||||||
|
line="13"
|
||||||
|
column="45"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.dimen.activity_horizontal_margin` appears to be unused"
|
||||||
|
errorLine1=" <dimen name="activity_horizontal_margin">16dp</dimen>"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/res/values/dimens.xml"
|
||||||
|
line="3"
|
||||||
|
column="12"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.dimen.activity_vertical_margin` appears to be unused"
|
||||||
|
errorLine1=" <dimen name="activity_vertical_margin">16dp</dimen>"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/res/values/dimens.xml"
|
||||||
|
line="4"
|
||||||
|
column="12"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.drawable.ic_dashboard_black_24dp` appears to be unused"
|
||||||
|
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/drawable/ic_dashboard_black_24dp.xml"
|
||||||
|
line="1"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.drawable.ic_home_black_24dp` appears to be unused"
|
||||||
|
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/drawable/ic_home_black_24dp.xml"
|
||||||
|
line="1"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.drawable.ic_notifications_black_24dp` appears to be unused"
|
||||||
|
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/drawable/ic_notifications_black_24dp.xml"
|
||||||
|
line="1"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="MonochromeLauncherIcon"
|
||||||
|
message="The application adaptive icon is missing a monochrome tag"
|
||||||
|
errorLine1="<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">"
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/mipmap-anydpi-v26/ic_launcher.xml"
|
||||||
|
line="2"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="MonochromeLauncherIcon"
|
||||||
|
message="The application adaptive roundIcon is missing a monochrome tag"
|
||||||
|
errorLine1="<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">"
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml"
|
||||||
|
line="2"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
</issues>
|
||||||
21
add_to_app/android_view/android_view/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.FlutterViewIntegration">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
// Copyright 2019 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package dev.flutter.example.androidView
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleObserver
|
||||||
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
|
import io.flutter.embedding.android.ExclusiveAppComponent
|
||||||
|
import io.flutter.embedding.android.FlutterView
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.platform.PlatformPlugin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an application-specific wrapper class that exists to expose the intersection of an
|
||||||
|
* application's active activity and an application's visible view to a [FlutterEngine] for
|
||||||
|
* rendering.
|
||||||
|
*
|
||||||
|
* Omitted features from the [io.flutter.embedding.android.FlutterActivity] include:
|
||||||
|
* * **State restoration**. If you're integrating at the view level, you should handle activity
|
||||||
|
* state restoration yourself.
|
||||||
|
* * **Engine creations**. At this level of granularity, you must make an engine and attach.
|
||||||
|
* and all engine features like initial route etc must be configured on the engine yourself.
|
||||||
|
* * **Splash screens**. You must implement it yourself. Read from
|
||||||
|
* `addOnFirstFrameRenderedListener` as needed.
|
||||||
|
* * **Transparency, surface/texture**. These are just [FlutterView] level APIs. Set them on the
|
||||||
|
* [FlutterView] directly.
|
||||||
|
* * **Intents**. This doesn't do any translation of intents into actions in the [FlutterEngine].
|
||||||
|
* you must do them yourself.
|
||||||
|
* * **Back buttons**. You must decide whether to send it to Flutter via
|
||||||
|
* [FlutterEngine.getNavigationChannel.popRoute()], or consume it natively. Though that
|
||||||
|
* decision may be difficult due to https://github.com/flutter/flutter/issues/67011.
|
||||||
|
* * **Low memory signals**. You're strongly encouraged to pass the low memory signals (such
|
||||||
|
* as from the host `Activity`'s `onTrimMemory` callbacks) to the [FlutterEngine] to let
|
||||||
|
* Flutter and the Dart VM cull its own memory usage.
|
||||||
|
*
|
||||||
|
* Your own [FlutterView] integrating application may need a similar wrapper but you must decide on
|
||||||
|
* what the appropriate intersection between the [FlutterView], the [FlutterEngine] and your
|
||||||
|
* `Activity` should be for your own application.
|
||||||
|
*/
|
||||||
|
class FlutterViewEngine(val engine: FlutterEngine) : LifecycleObserver, ExclusiveAppComponent<Activity>{
|
||||||
|
private var flutterView: FlutterView? = null
|
||||||
|
private var activity: ComponentActivity? = null
|
||||||
|
private var platformPlugin: PlatformPlugin? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the intersection of an available activity and of a visible [FlutterView]. This is
|
||||||
|
* where Flutter would start rendering.
|
||||||
|
*/
|
||||||
|
private fun hookActivityAndView() {
|
||||||
|
// Assert state.
|
||||||
|
activity!!.let { activity ->
|
||||||
|
flutterView!!.let { flutterView ->
|
||||||
|
platformPlugin = PlatformPlugin(activity, engine.platformChannel)
|
||||||
|
|
||||||
|
engine.activityControlSurface.attachToActivity(this, activity.lifecycle)
|
||||||
|
flutterView.attachToFlutterEngine(engine)
|
||||||
|
activity.lifecycle.addObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lost the intersection of either an available activity or a visible
|
||||||
|
* [FlutterView].
|
||||||
|
*/
|
||||||
|
private fun unhookActivityAndView() {
|
||||||
|
// Stop reacting to activity events.
|
||||||
|
activity!!.lifecycle.removeObserver(this)
|
||||||
|
|
||||||
|
// Plugins are no longer attached to an activity.
|
||||||
|
engine.activityControlSurface.detachFromActivity()
|
||||||
|
|
||||||
|
// Release Flutter's control of UI such as system chrome.
|
||||||
|
platformPlugin!!.destroy()
|
||||||
|
platformPlugin = null
|
||||||
|
|
||||||
|
// Set Flutter's application state to detached.
|
||||||
|
engine.lifecycleChannel.appIsDetached();
|
||||||
|
|
||||||
|
// Detach rendering pipeline.
|
||||||
|
flutterView!!.detachFromFlutterEngine()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that a host `Activity` is now ready. If there is no [FlutterView] instance currently
|
||||||
|
* attached to the view hierarchy and visible, Flutter is not yet rendering.
|
||||||
|
*
|
||||||
|
* You can also choose at this point whether to notify the plugins that an `Activity` is
|
||||||
|
* attached or not. You can also choose at this point whether to connect a Flutter
|
||||||
|
* [PlatformPlugin] at this point which allows your Dart program to trigger things like
|
||||||
|
* haptic feedback and read the clipboard. This sample arbitrarily chooses no for both.
|
||||||
|
*/
|
||||||
|
fun attachToActivity(activity: ComponentActivity) {
|
||||||
|
this.activity = activity
|
||||||
|
if (flutterView != null) {
|
||||||
|
hookActivityAndView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that a host `Activity` now no longer connected. If there were a [FlutterView] in
|
||||||
|
* the view hierarchy and visible at this moment, that [FlutterView] will stop rendering.
|
||||||
|
*
|
||||||
|
* You can also choose at this point whether to notify the plugins that an `Activity` is
|
||||||
|
* no longer attached or not. You can also choose at this point whether to disconnect Flutter's
|
||||||
|
* [PlatformPlugin] at this point which stops your Dart program being able to trigger things
|
||||||
|
* like haptic feedback and read the clipboard. This sample arbitrarily chooses yes for both.
|
||||||
|
*/
|
||||||
|
fun detachActivity() {
|
||||||
|
if (flutterView != null) {
|
||||||
|
unhookActivityAndView()
|
||||||
|
}
|
||||||
|
activity = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that a [FlutterView] instance is created and attached to a visible Android view
|
||||||
|
* hierarchy.
|
||||||
|
*
|
||||||
|
* If an `Activity` was also previously provided, this puts Flutter into the rendering state
|
||||||
|
* for this [FlutterView]. This also connects this wrapper class to listen to the `Activity`'s
|
||||||
|
* lifecycle to pause rendering when the activity is put into the background while the
|
||||||
|
* view is still attached to the view hierarchy.
|
||||||
|
*/
|
||||||
|
fun attachFlutterView(flutterView: FlutterView) {
|
||||||
|
this.flutterView = flutterView
|
||||||
|
if (activity != null) {
|
||||||
|
hookActivityAndView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that the attached [FlutterView] instance destroyed or no longer attached to a visible
|
||||||
|
* Android view hierarchy.
|
||||||
|
*
|
||||||
|
* If an `Activity` was attached, this stops Flutter from rendering. It also makes this wrapper
|
||||||
|
* class stop listening to the `Activity`'s lifecycle since it's no longer rendering.
|
||||||
|
*/
|
||||||
|
fun detachFlutterView() {
|
||||||
|
unhookActivityAndView()
|
||||||
|
flutterView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to let Flutter respond to the `Activity`'s resumed lifecycle event while both an
|
||||||
|
* `Activity` and a [FlutterView] are attached.
|
||||||
|
*/
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
|
private fun resumeActivity() {
|
||||||
|
if (activity != null) {
|
||||||
|
engine.lifecycleChannel.appIsResumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
platformPlugin?.updateSystemUiOverlays()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to let Flutter respond to the `Activity`'s paused lifecycle event while both an
|
||||||
|
* `Activity` and a [FlutterView] are attached.
|
||||||
|
*/
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||||
|
private fun pauseActivity() {
|
||||||
|
if (activity != null) {
|
||||||
|
engine.lifecycleChannel.appIsInactive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to let Flutter respond to the `Activity`'s stopped lifecycle event while both an
|
||||||
|
* `Activity` and a [FlutterView] are attached.
|
||||||
|
*/
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
|
private fun stopActivity() {
|
||||||
|
if (activity != null) {
|
||||||
|
engine.lifecycleChannel.appIsPaused()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These events aren't used but would be needed for Flutter plugins consuming
|
||||||
|
// these events to function.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass through the `Activity`'s `onRequestPermissionsResult` signal to plugins that may be
|
||||||
|
* listening to it while the `Activity` and the [FlutterView] are connected.
|
||||||
|
*/
|
||||||
|
fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
if (activity != null && flutterView != null) {
|
||||||
|
engine
|
||||||
|
.activityControlSurface
|
||||||
|
.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass through the `Activity`'s `onActivityResult` signal to plugins that may be
|
||||||
|
* listening to it while the `Activity` and the [FlutterView] are connected.
|
||||||
|
*/
|
||||||
|
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (activity != null && flutterView != null) {
|
||||||
|
engine.activityControlSurface.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass through the `Activity`'s `onUserLeaveHint` signal to plugins that may be
|
||||||
|
* listening to it while the `Activity` and the [FlutterView] are connected.
|
||||||
|
*/
|
||||||
|
fun onUserLeaveHint() {
|
||||||
|
if (activity != null && flutterView != null) {
|
||||||
|
engine.activityControlSurface.onUserLeaveHint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when another App Component is about to become attached to the [ ] this App Component
|
||||||
|
* is currently attached to.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This App Component's connections to the [io.flutter.embedding.engine.FlutterEngine]
|
||||||
|
* are still valid at the moment of this call.
|
||||||
|
*/
|
||||||
|
override fun detachFromFlutterEngine() {
|
||||||
|
// Do nothing here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the App Component behind this exclusive App Component.
|
||||||
|
*
|
||||||
|
* @return The app component.
|
||||||
|
*/
|
||||||
|
override fun getAppComponent(): Activity {
|
||||||
|
return activity!!;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2019 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package dev.flutter.example.androidView
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import dev.flutter.example.androidView.databinding.AndroidCardBinding
|
||||||
|
import io.flutter.embedding.android.FlutterView
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A demo-specific implementation of a [RecyclerView.Adapter] to setup the demo environment used
|
||||||
|
* to display view-level Flutter cells inside a list.
|
||||||
|
*
|
||||||
|
* The only instructional parts of this class are to show when to call
|
||||||
|
* [FlutterViewEngine.attachFlutterView] and [FlutterViewEngine.detachActivity] on a
|
||||||
|
* [FlutterViewEngine] equivalent class that you may want to create in your own application.
|
||||||
|
*/
|
||||||
|
class ListAdapter(context: Context, private val flutterViewEngine: FlutterViewEngine) : RecyclerView.Adapter<ListAdapter.Cell>() {
|
||||||
|
// Save the previous cells determined to be Flutter cells to avoid a confusing visual effect
|
||||||
|
// that the Flutter cells change position when scrolling back.
|
||||||
|
var previousFlutterCells = TreeSet<Int>();
|
||||||
|
|
||||||
|
private val matchParentLayout = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
|
|
||||||
|
private val random = Random.Default
|
||||||
|
private val flutterView = FlutterView(context)
|
||||||
|
private val flutterChannel = MethodChannel(flutterViewEngine.engine.dartExecutor, "dev.flutter.example/cell")
|
||||||
|
|
||||||
|
private var flutterCell: Cell? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [RecyclerView.ViewHolder] based on the `android_card` layout XML.
|
||||||
|
*/
|
||||||
|
inner class Cell(val binding: AndroidCardBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): Cell {
|
||||||
|
val binding = AndroidCardBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||||
|
|
||||||
|
// Let the default view holder have an "Android card" inflated from the layout XML. When
|
||||||
|
// needed, hide the Android card and show a Flutter one instead.
|
||||||
|
return Cell(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(cell: Cell, position: Int) {
|
||||||
|
// While scrolling forward, if no Flutter is presently showing, let the next one have a 1/3
|
||||||
|
// chance of being Flutter.
|
||||||
|
//
|
||||||
|
// While scrolling backward, let it be deterministic, and only show cells that were
|
||||||
|
// previously Flutter cells as Flutter cells.
|
||||||
|
if (previousFlutterCells.contains(position)
|
||||||
|
|| ((previousFlutterCells.isEmpty() || position > previousFlutterCells.last())
|
||||||
|
&& flutterCell == null
|
||||||
|
&& random.nextInt(3) == 0)) {
|
||||||
|
// If we're restoring a cell at a previous location, the current cell may not have
|
||||||
|
// recycled yet since that JVM timing is indeterministic. Yank it from the current one.
|
||||||
|
//
|
||||||
|
// This shouldn't produce any visual glitches since in the forward direction,
|
||||||
|
// Flutter cells were only introduced once the previous Flutter cell recycled.
|
||||||
|
if (flutterCell != null) {
|
||||||
|
Log.w("FeedAdapter", "While restoring a previous Flutter cell, a current "
|
||||||
|
+ "yet to be recycled Flutter cell was detached.")
|
||||||
|
flutterCell!!.binding.root.removeView(flutterView)
|
||||||
|
flutterViewEngine.detachFlutterView()
|
||||||
|
flutterCell = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the Flutter card and hide the Android card for the cells chosen to be Flutter
|
||||||
|
// cells.
|
||||||
|
cell.binding.root.addView(flutterView, matchParentLayout)
|
||||||
|
cell.binding.androidCard.visibility = View.GONE
|
||||||
|
|
||||||
|
// Keep track of the cell so we know which one to restore back to the "Android cell"
|
||||||
|
// state when the view gets recycled.
|
||||||
|
flutterCell = cell
|
||||||
|
// Keep track that this position has once been a Flutter cell. Let it be a Flutter cell
|
||||||
|
// again when scrolling back to this position.
|
||||||
|
previousFlutterCells.add(position)
|
||||||
|
|
||||||
|
// This is what makes the Flutter cell start rendering.
|
||||||
|
flutterViewEngine.attachFlutterView(flutterView)
|
||||||
|
// Tell Flutter which index it's at so Flutter could show the cell number too in its
|
||||||
|
// own widget tree.
|
||||||
|
flutterChannel.invokeMethod("setCellNumber", position)
|
||||||
|
} else {
|
||||||
|
// If it's not selected as a Flutter cell, just show the Android card.
|
||||||
|
cell.binding.androidCard.visibility = View.VISIBLE
|
||||||
|
cell.binding.cellNumber.text = position.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = 100
|
||||||
|
|
||||||
|
override fun onViewRecycled(cell: Cell) {
|
||||||
|
if (cell == flutterCell) {
|
||||||
|
cell.binding.root.removeView(flutterView)
|
||||||
|
flutterViewEngine.detachFlutterView()
|
||||||
|
flutterCell = null
|
||||||
|
}
|
||||||
|
super.onViewRecycled(cell)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright 2019 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package dev.flutter.example.androidView
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.os.BundleCompat
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import dev.flutter.example.androidView.databinding.ActivityMainBinding
|
||||||
|
import io.flutter.FlutterInjector
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.embedding.engine.dart.DartExecutor
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
// There are 3 files in this sample. MainActivity and ListAdapter are just
|
||||||
|
// fictional setups. FlutterViewEngine is instructional and demonstrates the
|
||||||
|
// various plumbing needed for a functioning FlutterView integration.
|
||||||
|
/**
|
||||||
|
* Main activity for this demo that shows a page with a `RecyclerView`.
|
||||||
|
*
|
||||||
|
* There are 3 files in this sample. MainActivity and ListAdapter are just fictional setups.
|
||||||
|
* FlutterViewEngine is instructional and demonstrates the various plumbing needed for a functioning
|
||||||
|
* FlutterView integration.
|
||||||
|
*/
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
private lateinit var flutterViewEngine: FlutterViewEngine
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
// TODO: create a multi-engine version after
|
||||||
|
// https://github.com/flutter/flutter/issues/72009 is built.
|
||||||
|
val engine = FlutterEngine(applicationContext)
|
||||||
|
engine.dartExecutor.executeDartEntrypoint(
|
||||||
|
DartExecutor.DartEntrypoint(
|
||||||
|
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
|
||||||
|
"showCell"))
|
||||||
|
|
||||||
|
flutterViewEngine = FlutterViewEngine(engine)
|
||||||
|
// The activity and FlutterView have different lifecycles.
|
||||||
|
// Attach the activity right away but only start rendering when the
|
||||||
|
// view is also scrolled into the screen.
|
||||||
|
flutterViewEngine.attachToActivity(this)
|
||||||
|
|
||||||
|
val layoutManager = LinearLayoutManager(this)
|
||||||
|
val recyclerView = binding.recyclerView
|
||||||
|
val adapter = ListAdapter(this, flutterViewEngine)
|
||||||
|
recyclerView.layoutManager = layoutManager
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
// If the activity was restarted, keep track of the previous scroll
|
||||||
|
// position and of the previous cell indices that were randomly selected
|
||||||
|
// as Flutter cells to preserve immersion.
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
val state = BundleCompat.getParcelable<Parcelable>(
|
||||||
|
savedInstanceState,
|
||||||
|
"layoutManager",
|
||||||
|
Parcelable::class.java
|
||||||
|
)
|
||||||
|
layoutManager.onRestoreInstanceState(state)
|
||||||
|
}
|
||||||
|
val previousFlutterCellsArray = savedInstanceState?.getIntegerArrayList("adapter")
|
||||||
|
if (previousFlutterCellsArray != null) {
|
||||||
|
adapter.previousFlutterCells = TreeSet(previousFlutterCellsArray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
|
outState.putParcelable("layoutManager", binding.recyclerView.layoutManager?.onSaveInstanceState())
|
||||||
|
val previousFlutterCells = (binding.recyclerView.adapter as? ListAdapter)?.previousFlutterCells
|
||||||
|
if (previousFlutterCells != null) {
|
||||||
|
outState.putIntegerArrayList(
|
||||||
|
"adapter",
|
||||||
|
ArrayList(previousFlutterCells)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
flutterViewEngine.detachActivity()
|
||||||
|
}
|
||||||
|
|
||||||
|
// These below aren't used here in this demo but would be needed for Flutter plugins that may
|
||||||
|
// consume these events.
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
flutterViewEngine.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
flutterViewEngine.onActivityResult(requestCode, resultCode, data)
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserLeaveHint() {
|
||||||
|
flutterViewEngine.onUserLeaveHint()
|
||||||
|
super.onUserLeaveHint()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="160dp">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/androidCard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginHorizontal="36dp"
|
||||||
|
android:layout_marginVertical="24dp"
|
||||||
|
app:cardBackgroundColor="#F1F1F1"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="16dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/cellNumber"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:textAppearance="?attr/textAppearanceHeadline3"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
</FrameLayout>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.FlutterViewIntegration" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_200</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/black</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">Flutter View Integration</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.FlutterViewIntegration" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
26
add_to_app/android_view/android_view/build.gradle
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.8.22'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.9.1'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('clean', Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
21
add_to_app/android_view/android_view/gradle.properties
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app"s APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
|
kotlin.code.style=official
|
||||||
|
android.nonTransitiveRClass=false
|
||||||
|
android.nonFinalResIds=false
|
||||||
6
add_to_app/android_view/android_view/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#Fri Jul 26 11:31:21 EDT 2024
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
172
add_to_app/android_view/android_view/gradlew
vendored
Executable file
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
84
add_to_app/android_view/android_view/gradlew.bat
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
7
add_to_app/android_view/android_view/settings.gradle
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
rootProject.name = "Flutter View Integration"
|
||||||
|
include ':app'
|
||||||
|
setBinding(new Binding([gradle: this]))
|
||||||
|
evaluate(new File(
|
||||||
|
settingsDir.parentFile,
|
||||||
|
'flutter_module_using_plugin_android_view/.android/include_flutter.groovy'
|
||||||
|
))
|
||||||
15
add_to_app/android_view/content_sizing_android_view/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# android_view
|
||||||
|
|
||||||
|
An example of an Android app that integrates a Flutter add-to-app module at a
|
||||||
|
view level. For more information see [../README.md](../README.md).
|
||||||
1
add_to_app/android_view/content_sizing_android_view/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'kotlin-android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 36
|
||||||
|
|
||||||
|
lint {
|
||||||
|
baseline = file("lint-baseline.xml")
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "dev.flutter.example.androidView"
|
||||||
|
minSdkVersion 24
|
||||||
|
targetSdk 36
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
namespace 'dev.flutter.example.androidView'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
implementation 'androidx.core:core-ktx:1.13.1'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.vectordrawable:vectordrawable:1.2.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
|
||||||
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
||||||
|
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
||||||
|
implementation "androidx.recyclerview:recyclerview:1.3.2"
|
||||||
|
implementation project(path: ':flutter')
|
||||||
|
testImplementation 'junit:junit:4.+'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<issues format="6" by="lint 7.4.2" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.2)" variant="all" version="7.4.2">
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="RedundantLabel"
|
||||||
|
message="Redundant label can be removed"
|
||||||
|
errorLine1=" android:label="@string/app_name">"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/AndroidManifest.xml"
|
||||||
|
line="14"
|
||||||
|
column="13"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of org.jetbrains.kotlin:kotlin-stdlib than 1.8.22 is available: 1.9.20"
|
||||||
|
errorLine1="}"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="39"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.core:core-ktx than 1.3.2 is available: 1.13.1"
|
||||||
|
errorLine1=""
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="40"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.appcompat:appcompat than 1.2.0 is available: 1.7.0"
|
||||||
|
errorLine1="dependencies {"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="41"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of com.google.android.material:material than 1.2.1 is available: 1.12.0"
|
||||||
|
errorLine1=" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version""
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="42"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.constraintlayout:constraintlayout than 2.0.4 is available: 2.1.4"
|
||||||
|
errorLine1=" implementation 'androidx.core:core-ktx:1.3.2'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="43"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.vectordrawable:vectordrawable than 1.1.0 is available: 1.2.0"
|
||||||
|
errorLine1=" implementation 'androidx.appcompat:appcompat:1.2.0'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="44"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.lifecycle:lifecycle-livedata-ktx than 2.2.0 is available: 2.8.2"
|
||||||
|
errorLine1=" implementation 'com.google.android.material:material:1.2.1'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="45"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.lifecycle:lifecycle-viewmodel-ktx than 2.2.0 is available: 2.8.2"
|
||||||
|
errorLine1=" implementation 'androidx.constraintlayout:constraintlayout:2.0.4'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="46"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.navigation:navigation-fragment-ktx than 2.3.2 is available: 2.7.7"
|
||||||
|
errorLine1=" implementation 'androidx.vectordrawable:vectordrawable:1.1.0'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="47"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.navigation:navigation-ui-ktx than 2.3.2 is available: 2.7.7"
|
||||||
|
errorLine1=" implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="48"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.recyclerview:recyclerview than 1.1.0 is available: 1.3.2"
|
||||||
|
errorLine1=" implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="49"
|
||||||
|
column="20"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.test.ext:junit than 1.1.2 is available: 1.1.5"
|
||||||
|
errorLine1=" implementation "androidx.recyclerview:recyclerview:1.1.0""
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="52"
|
||||||
|
column="31"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDependency"
|
||||||
|
message="A newer version of androidx.test.espresso:espresso-core than 3.3.0 is available: 3.5.1"
|
||||||
|
errorLine1=" implementation project(path: ':flutter')"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="53"
|
||||||
|
column="31"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="GradleDynamicVersion"
|
||||||
|
message="Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (junit:junit:4.+)"
|
||||||
|
errorLine1=" implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="51"
|
||||||
|
column="24"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="ExpiredTargetSdkVersion"
|
||||||
|
message="Google Play requires that apps target API level 31 or higher.
"
|
||||||
|
errorLine1=" defaultConfig {"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="build.gradle"
|
||||||
|
line="12"
|
||||||
|
column="9"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="ObsoleteSdkInt"
|
||||||
|
message="Unnecessary; SDK_INT is always >= 21"
|
||||||
|
errorLine1=" <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/res/values-night/themes.xml"
|
||||||
|
line="13"
|
||||||
|
column="45"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="ObsoleteSdkInt"
|
||||||
|
message="Unnecessary; SDK_INT is always >= 21"
|
||||||
|
errorLine1=" <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/res/values/themes.xml"
|
||||||
|
line="13"
|
||||||
|
column="45"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.dimen.activity_horizontal_margin` appears to be unused"
|
||||||
|
errorLine1=" <dimen name="activity_horizontal_margin">16dp</dimen>"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/res/values/dimens.xml"
|
||||||
|
line="3"
|
||||||
|
column="12"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.dimen.activity_vertical_margin` appears to be unused"
|
||||||
|
errorLine1=" <dimen name="activity_vertical_margin">16dp</dimen>"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/res/values/dimens.xml"
|
||||||
|
line="4"
|
||||||
|
column="12"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.drawable.ic_dashboard_black_24dp` appears to be unused"
|
||||||
|
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/drawable/ic_dashboard_black_24dp.xml"
|
||||||
|
line="1"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.drawable.ic_home_black_24dp` appears to be unused"
|
||||||
|
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/drawable/ic_home_black_24dp.xml"
|
||||||
|
line="1"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.drawable.ic_notifications_black_24dp` appears to be unused"
|
||||||
|
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/drawable/ic_notifications_black_24dp.xml"
|
||||||
|
line="1"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="MonochromeLauncherIcon"
|
||||||
|
message="The application adaptive icon is missing a monochrome tag"
|
||||||
|
errorLine1="<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">"
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/mipmap-anydpi-v26/ic_launcher.xml"
|
||||||
|
line="2"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="MonochromeLauncherIcon"
|
||||||
|
message="The application adaptive roundIcon is missing a monochrome tag"
|
||||||
|
errorLine1="<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">"
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml"
|
||||||
|
line="2"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
</issues>
|
||||||
21
add_to_app/android_view/content_sizing_android_view/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.FlutterViewIntegration">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.EnableContentSizing"
|
||||||
|
android:value="true" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
// Copyright 2019 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package dev.flutter.example.androidView
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleObserver
|
||||||
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
|
import io.flutter.embedding.android.ExclusiveAppComponent
|
||||||
|
import io.flutter.embedding.android.FlutterView
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.platform.PlatformPlugin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an application-specific wrapper class that exists to expose the intersection of an
|
||||||
|
* application's active activity and an application's visible view to a [FlutterEngine] for
|
||||||
|
* rendering.
|
||||||
|
*
|
||||||
|
* Omitted features from the [io.flutter.embedding.android.FlutterActivity] include:
|
||||||
|
* * **State restoration**. If you're integrating at the view level, you should handle activity
|
||||||
|
* state restoration yourself.
|
||||||
|
* * **Engine creations**. At this level of granularity, you must make an engine and attach.
|
||||||
|
* and all engine features like initial route etc must be configured on the engine yourself.
|
||||||
|
* * **Splash screens**. You must implement it yourself. Read from
|
||||||
|
* `addOnFirstFrameRenderedListener` as needed.
|
||||||
|
* * **Transparency, surface/texture**. These are just [FlutterView] level APIs. Set them on the
|
||||||
|
* [FlutterView] directly.
|
||||||
|
* * **Intents**. This doesn't do any translation of intents into actions in the [FlutterEngine].
|
||||||
|
* you must do them yourself.
|
||||||
|
* * **Back buttons**. You must decide whether to send it to Flutter via
|
||||||
|
* [FlutterEngine.getNavigationChannel.popRoute()], or consume it natively. Though that
|
||||||
|
* decision may be difficult due to https://github.com/flutter/flutter/issues/67011.
|
||||||
|
* * **Low memory signals**. You're strongly encouraged to pass the low memory signals (such
|
||||||
|
* as from the host `Activity`'s `onTrimMemory` callbacks) to the [FlutterEngine] to let
|
||||||
|
* Flutter and the Dart VM cull its own memory usage.
|
||||||
|
*
|
||||||
|
* Your own [FlutterView] integrating application may need a similar wrapper but you must decide on
|
||||||
|
* what the appropriate intersection between the [FlutterView], the [FlutterEngine] and your
|
||||||
|
* `Activity` should be for your own application.
|
||||||
|
*/
|
||||||
|
class FlutterViewEngine(val engine: FlutterEngine) : LifecycleObserver, ExclusiveAppComponent<Activity>{
|
||||||
|
private var flutterView: FlutterView? = null
|
||||||
|
private var activity: ComponentActivity? = null
|
||||||
|
private var platformPlugin: PlatformPlugin? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the intersection of an available activity and of a visible [FlutterView]. This is
|
||||||
|
* where Flutter would start rendering.
|
||||||
|
*/
|
||||||
|
private fun hookActivityAndView() {
|
||||||
|
// Assert state.
|
||||||
|
activity!!.let { activity ->
|
||||||
|
flutterView!!.let { flutterView ->
|
||||||
|
platformPlugin = PlatformPlugin(activity, engine.platformChannel)
|
||||||
|
|
||||||
|
engine.activityControlSurface.attachToActivity(this, activity.lifecycle)
|
||||||
|
flutterView.attachToFlutterEngine(engine)
|
||||||
|
activity.lifecycle.addObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lost the intersection of either an available activity or a visible
|
||||||
|
* [FlutterView].
|
||||||
|
*/
|
||||||
|
private fun unhookActivityAndView() {
|
||||||
|
// Stop reacting to activity events.
|
||||||
|
activity!!.lifecycle.removeObserver(this)
|
||||||
|
|
||||||
|
// Plugins are no longer attached to an activity.
|
||||||
|
engine.activityControlSurface.detachFromActivity()
|
||||||
|
|
||||||
|
// Release Flutter's control of UI such as system chrome.
|
||||||
|
platformPlugin!!.destroy()
|
||||||
|
platformPlugin = null
|
||||||
|
|
||||||
|
// Set Flutter's application state to detached.
|
||||||
|
engine.lifecycleChannel.appIsDetached();
|
||||||
|
|
||||||
|
// Detach rendering pipeline.
|
||||||
|
flutterView!!.detachFromFlutterEngine()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that a host `Activity` is now ready. If there is no [FlutterView] instance currently
|
||||||
|
* attached to the view hierarchy and visible, Flutter is not yet rendering.
|
||||||
|
*
|
||||||
|
* You can also choose at this point whether to notify the plugins that an `Activity` is
|
||||||
|
* attached or not. You can also choose at this point whether to connect a Flutter
|
||||||
|
* [PlatformPlugin] at this point which allows your Dart program to trigger things like
|
||||||
|
* haptic feedback and read the clipboard. This sample arbitrarily chooses no for both.
|
||||||
|
*/
|
||||||
|
fun attachToActivity(activity: ComponentActivity) {
|
||||||
|
this.activity = activity
|
||||||
|
if (flutterView != null) {
|
||||||
|
hookActivityAndView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that a host `Activity` now no longer connected. If there were a [FlutterView] in
|
||||||
|
* the view hierarchy and visible at this moment, that [FlutterView] will stop rendering.
|
||||||
|
*
|
||||||
|
* You can also choose at this point whether to notify the plugins that an `Activity` is
|
||||||
|
* no longer attached or not. You can also choose at this point whether to disconnect Flutter's
|
||||||
|
* [PlatformPlugin] at this point which stops your Dart program being able to trigger things
|
||||||
|
* like haptic feedback and read the clipboard. This sample arbitrarily chooses yes for both.
|
||||||
|
*/
|
||||||
|
fun detachActivity() {
|
||||||
|
if (flutterView != null) {
|
||||||
|
unhookActivityAndView()
|
||||||
|
}
|
||||||
|
activity = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that a [FlutterView] instance is created and attached to a visible Android view
|
||||||
|
* hierarchy.
|
||||||
|
*
|
||||||
|
* If an `Activity` was also previously provided, this puts Flutter into the rendering state
|
||||||
|
* for this [FlutterView]. This also connects this wrapper class to listen to the `Activity`'s
|
||||||
|
* lifecycle to pause rendering when the activity is put into the background while the
|
||||||
|
* view is still attached to the view hierarchy.
|
||||||
|
*/
|
||||||
|
fun attachFlutterView(flutterView: FlutterView) {
|
||||||
|
this.flutterView = flutterView
|
||||||
|
if (activity != null) {
|
||||||
|
hookActivityAndView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that the attached [FlutterView] instance destroyed or no longer attached to a visible
|
||||||
|
* Android view hierarchy.
|
||||||
|
*
|
||||||
|
* If an `Activity` was attached, this stops Flutter from rendering. It also makes this wrapper
|
||||||
|
* class stop listening to the `Activity`'s lifecycle since it's no longer rendering.
|
||||||
|
*/
|
||||||
|
fun detachFlutterView() {
|
||||||
|
unhookActivityAndView()
|
||||||
|
flutterView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to let Flutter respond to the `Activity`'s resumed lifecycle event while both an
|
||||||
|
* `Activity` and a [FlutterView] are attached.
|
||||||
|
*/
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
|
private fun resumeActivity() {
|
||||||
|
if (activity != null) {
|
||||||
|
engine.lifecycleChannel.appIsResumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
platformPlugin?.updateSystemUiOverlays()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to let Flutter respond to the `Activity`'s paused lifecycle event while both an
|
||||||
|
* `Activity` and a [FlutterView] are attached.
|
||||||
|
*/
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||||
|
private fun pauseActivity() {
|
||||||
|
if (activity != null) {
|
||||||
|
engine.lifecycleChannel.appIsInactive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to let Flutter respond to the `Activity`'s stopped lifecycle event while both an
|
||||||
|
* `Activity` and a [FlutterView] are attached.
|
||||||
|
*/
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
|
private fun stopActivity() {
|
||||||
|
if (activity != null) {
|
||||||
|
engine.lifecycleChannel.appIsPaused()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These events aren't used but would be needed for Flutter plugins consuming
|
||||||
|
// these events to function.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass through the `Activity`'s `onRequestPermissionsResult` signal to plugins that may be
|
||||||
|
* listening to it while the `Activity` and the [FlutterView] are connected.
|
||||||
|
*/
|
||||||
|
fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
if (activity != null && flutterView != null) {
|
||||||
|
engine
|
||||||
|
.activityControlSurface
|
||||||
|
.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass through the `Activity`'s `onActivityResult` signal to plugins that may be
|
||||||
|
* listening to it while the `Activity` and the [FlutterView] are connected.
|
||||||
|
*/
|
||||||
|
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (activity != null && flutterView != null) {
|
||||||
|
engine.activityControlSurface.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass through the `Activity`'s `onUserLeaveHint` signal to plugins that may be
|
||||||
|
* listening to it while the `Activity` and the [FlutterView] are connected.
|
||||||
|
*/
|
||||||
|
fun onUserLeaveHint() {
|
||||||
|
if (activity != null && flutterView != null) {
|
||||||
|
engine.activityControlSurface.onUserLeaveHint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when another App Component is about to become attached to the [ ] this App Component
|
||||||
|
* is currently attached to.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This App Component's connections to the [io.flutter.embedding.engine.FlutterEngine]
|
||||||
|
* are still valid at the moment of this call.
|
||||||
|
*/
|
||||||
|
override fun detachFromFlutterEngine() {
|
||||||
|
// Do nothing here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the App Component behind this exclusive App Component.
|
||||||
|
*
|
||||||
|
* @return The app component.
|
||||||
|
*/
|
||||||
|
override fun getAppComponent(): Activity {
|
||||||
|
return activity!!;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2019 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package dev.flutter.example.androidView
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import dev.flutter.example.androidView.databinding.AndroidCardBinding
|
||||||
|
import io.flutter.embedding.android.FlutterView
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A demo-specific implementation of a [RecyclerView.Adapter] to setup the demo environment used
|
||||||
|
* to display view-level Flutter cells inside a list.
|
||||||
|
*
|
||||||
|
* The only instructional parts of this class are to show when to call
|
||||||
|
* [FlutterViewEngine.attachFlutterView] and [FlutterViewEngine.detachActivity] on a
|
||||||
|
* [FlutterViewEngine] equivalent class that you may want to create in your own application.
|
||||||
|
*/
|
||||||
|
class ListAdapter(context: Context, private val flutterViewEngine: FlutterViewEngine) : RecyclerView.Adapter<ListAdapter.Cell>() {
|
||||||
|
// Save the previous cells determined to be Flutter cells to avoid a confusing visual effect
|
||||||
|
// that the Flutter cells change position when scrolling back.
|
||||||
|
var previousFlutterCells = TreeSet<Int>();
|
||||||
|
|
||||||
|
private val wrapContentLayout = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
|
||||||
|
private val random = Random.Default
|
||||||
|
private val flutterView = FlutterView(context)
|
||||||
|
private var flutterCell: Cell? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [RecyclerView.ViewHolder] based on the `android_card` layout XML.
|
||||||
|
*/
|
||||||
|
inner class Cell(val binding: AndroidCardBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): Cell {
|
||||||
|
val binding = AndroidCardBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||||
|
|
||||||
|
// Let the default view holder have an "Android card" inflated from the layout XML. When
|
||||||
|
// needed, hide the Android card and show a Flutter one instead.
|
||||||
|
return Cell(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(cell: Cell, position: Int) {
|
||||||
|
// While scrolling forward, if no Flutter is presently showing, let the next one have a 1/3
|
||||||
|
// chance of being Flutter.
|
||||||
|
//
|
||||||
|
// While scrolling backward, let it be deterministic, and only show cells that were
|
||||||
|
// previously Flutter cells as Flutter cells.
|
||||||
|
if (previousFlutterCells.contains(position)
|
||||||
|
|| ((previousFlutterCells.isEmpty() || position > previousFlutterCells.last())
|
||||||
|
&& flutterCell == null
|
||||||
|
&& random.nextInt(3) == 0)) {
|
||||||
|
// If we're restoring a cell at a previous location, the current cell may not have
|
||||||
|
// recycled yet since that JVM timing is indeterministic. Yank it from the current one.
|
||||||
|
//
|
||||||
|
// This shouldn't produce any visual glitches since in the forward direction,
|
||||||
|
// Flutter cells were only introduced once the previous Flutter cell recycled.
|
||||||
|
if (flutterCell != null) {
|
||||||
|
Log.w("FeedAdapter", "While restoring a previous Flutter cell, a current "
|
||||||
|
+ "yet to be recycled Flutter cell was detached.")
|
||||||
|
flutterCell!!.binding.root.removeView(flutterView)
|
||||||
|
flutterViewEngine.detachFlutterView()
|
||||||
|
flutterCell = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the Flutter card and hide the Android card for the cells chosen to be Flutter
|
||||||
|
// cells.
|
||||||
|
cell.binding.root.addView(flutterView, wrapContentLayout)
|
||||||
|
cell.binding.androidCard.visibility = View.GONE
|
||||||
|
|
||||||
|
// Keep track of the cell so we know which one to restore back to the "Android cell"
|
||||||
|
// state when the view gets recycled.
|
||||||
|
flutterCell = cell
|
||||||
|
// Keep track that this position has once been a Flutter cell. Let it be a Flutter cell
|
||||||
|
// again when scrolling back to this position.
|
||||||
|
previousFlutterCells.add(position)
|
||||||
|
|
||||||
|
// This is what makes the Flutter cell start rendering.
|
||||||
|
flutterViewEngine.attachFlutterView(flutterView)
|
||||||
|
} else {
|
||||||
|
// If it's not selected as a Flutter cell, just show the Android card.
|
||||||
|
cell.binding.androidCard.visibility = View.VISIBLE
|
||||||
|
cell.binding.cellNumber.text = position.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = 100
|
||||||
|
|
||||||
|
override fun onViewRecycled(cell: Cell) {
|
||||||
|
if (cell == flutterCell) {
|
||||||
|
cell.binding.root.removeView(flutterView)
|
||||||
|
flutterViewEngine.detachFlutterView()
|
||||||
|
flutterCell = null
|
||||||
|
}
|
||||||
|
super.onViewRecycled(cell)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright 2019 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package dev.flutter.example.androidView
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.os.BundleCompat
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import dev.flutter.example.androidView.databinding.ActivityMainBinding
|
||||||
|
import io.flutter.FlutterInjector
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.embedding.engine.dart.DartExecutor
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
// There are 3 files in this sample. MainActivity and ListAdapter are just
|
||||||
|
// fictional setups. FlutterViewEngine is instructional and demonstrates the
|
||||||
|
// various plumbing needed for a functioning FlutterView integration.
|
||||||
|
/**
|
||||||
|
* Main activity for this demo that shows a page with a `RecyclerView`.
|
||||||
|
*
|
||||||
|
* There are 3 files in this sample. MainActivity and ListAdapter are just fictional setups.
|
||||||
|
* FlutterViewEngine is instructional and demonstrates the various plumbing needed for a functioning
|
||||||
|
* FlutterView integration.
|
||||||
|
*/
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
private lateinit var flutterViewEngine: FlutterViewEngine
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
// TODO: create a multi-engine version after
|
||||||
|
// https://github.com/flutter/flutter/issues/72009 is built.
|
||||||
|
val engine = FlutterEngine(applicationContext)
|
||||||
|
engine.dartExecutor.executeDartEntrypoint(
|
||||||
|
DartExecutor.DartEntrypoint(
|
||||||
|
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
|
||||||
|
"main"))
|
||||||
|
|
||||||
|
flutterViewEngine = FlutterViewEngine(engine)
|
||||||
|
// The activity and FlutterView have different lifecycles.
|
||||||
|
// Attach the activity right away but only start rendering when the
|
||||||
|
// view is also scrolled into the screen.
|
||||||
|
flutterViewEngine.attachToActivity(this)
|
||||||
|
|
||||||
|
val layoutManager = LinearLayoutManager(this)
|
||||||
|
val recyclerView = binding.recyclerView
|
||||||
|
val adapter = ListAdapter(this, flutterViewEngine)
|
||||||
|
recyclerView.layoutManager = layoutManager
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
// If the activity was restarted, keep track of the previous scroll
|
||||||
|
// position and of the previous cell indices that were randomly selected
|
||||||
|
// as Flutter cells to preserve immersion.
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
val state = BundleCompat.getParcelable<Parcelable>(
|
||||||
|
savedInstanceState,
|
||||||
|
"layoutManager",
|
||||||
|
Parcelable::class.java
|
||||||
|
)
|
||||||
|
layoutManager.onRestoreInstanceState(state)
|
||||||
|
}
|
||||||
|
val previousFlutterCellsArray = savedInstanceState?.getIntegerArrayList("adapter")
|
||||||
|
if (previousFlutterCellsArray != null) {
|
||||||
|
adapter.previousFlutterCells = TreeSet(previousFlutterCellsArray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
|
outState.putParcelable("layoutManager", binding.recyclerView.layoutManager?.onSaveInstanceState())
|
||||||
|
val previousFlutterCells = (binding.recyclerView.adapter as? ListAdapter)?.previousFlutterCells
|
||||||
|
if (previousFlutterCells != null) {
|
||||||
|
outState.putIntegerArrayList(
|
||||||
|
"adapter",
|
||||||
|
ArrayList(previousFlutterCells)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
flutterViewEngine.detachActivity()
|
||||||
|
}
|
||||||
|
|
||||||
|
// These below aren't used here in this demo but would be needed for Flutter plugins that may
|
||||||
|
// consume these events.
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
flutterViewEngine.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
flutterViewEngine.onActivityResult(requestCode, resultCode, data)
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserLeaveHint() {
|
||||||
|
flutterViewEngine.onUserLeaveHint()
|
||||||
|
super.onUserLeaveHint()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/androidCard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginHorizontal="36dp"
|
||||||
|
android:layout_marginVertical="24dp"
|
||||||
|
app:cardBackgroundColor="#F1F1F1"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="16dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/cellNumber"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:textAppearance="?attr/textAppearanceHeadline3"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
</FrameLayout>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.FlutterViewIntegration" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_200</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/black</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">Flutter View Content Sizing Integration</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.FlutterViewIntegration" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.8.22'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.9.1'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('clean', Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||